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        if (!m_document->settings()->linkPrefetchEnabled())
297            return false;
298        break;
299#endif
300    }
301
302    return true;
303}
304
305CachedResource* CachedResourceLoader::requestResource(CachedResource::Type type, const String& resourceURL, const String& charset, ResourceLoadPriority priority, bool forPreload)
306{
307    KURL url = m_document->completeURL(resourceURL);
308
309    LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.string().latin1().data(), charset.latin1().data(), priority, forPreload);
310
311    // If only the fragment identifiers differ, it is the same resource.
312    url = MemoryCache::removeFragmentIdentifierIfNeeded(url);
313
314    if (!url.isValid())
315        return 0;
316
317    if (!canRequest(type, url))
318        return 0;
319
320    // FIXME: Figure out what is the correct way to merge this security check with the one above.
321    if (!document()->securityOrigin()->canDisplay(url)) {
322        if (!forPreload)
323            FrameLoader::reportLocalLoadFailed(document()->frame(), url.string());
324        LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
325        return 0;
326    }
327
328    if (memoryCache()->disabled()) {
329        DocumentResourceMap::iterator it = m_documentResources.find(url.string());
330        if (it != m_documentResources.end()) {
331            it->second->setOwningCachedResourceLoader(0);
332            m_documentResources.remove(it);
333        }
334    }
335
336    // See if we can use an existing resource from the cache.
337    CachedResource* resource = memoryCache()->resourceForURL(url);
338
339    switch (determineRevalidationPolicy(type, forPreload, resource)) {
340    case Load:
341        resource = loadResource(type, url, charset, priority);
342        break;
343    case Reload:
344        memoryCache()->remove(resource);
345        resource = loadResource(type, url, charset, priority);
346        break;
347    case Revalidate:
348        resource = revalidateResource(resource, priority);
349        break;
350    case Use:
351        memoryCache()->resourceAccessed(resource);
352        notifyLoadedFromMemoryCache(resource);
353        break;
354    }
355
356    if (!resource)
357        return 0;
358
359    ASSERT(resource->url() == url.string());
360    m_documentResources.set(resource->url(), resource);
361
362    return resource;
363}
364
365CachedResource* CachedResourceLoader::revalidateResource(CachedResource* resource, ResourceLoadPriority priority)
366{
367    ASSERT(resource);
368    ASSERT(resource->inCache());
369    ASSERT(!memoryCache()->disabled());
370    ASSERT(resource->canUseCacheValidator());
371    ASSERT(!resource->resourceToRevalidate());
372
373    // Copy the URL out of the resource to be revalidated in case it gets deleted by the remove() call below.
374    String url = resource->url();
375    CachedResource* newResource = createResource(resource->type(), KURL(ParsedURLString, url), resource->encoding());
376
377    LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource, resource);
378    newResource->setResourceToRevalidate(resource);
379
380    memoryCache()->remove(resource);
381    memoryCache()->add(newResource);
382
383    newResource->setLoadPriority(priority);
384    newResource->load(this);
385
386    m_validatedURLs.add(url);
387    return newResource;
388}
389
390CachedResource* CachedResourceLoader::loadResource(CachedResource::Type type, const KURL& url, const String& charset, ResourceLoadPriority priority)
391{
392    ASSERT(!memoryCache()->resourceForURL(url));
393
394    LOG(ResourceLoading, "Loading CachedResource for '%s'.", url.string().latin1().data());
395
396    CachedResource* resource = createResource(type, url, charset);
397
398    bool inCache = memoryCache()->add(resource);
399
400    // Pretend the resource is in the cache, to prevent it from being deleted during the load() call.
401    // FIXME: CachedResource should just use normal refcounting instead.
402    if (!inCache)
403        resource->setInCache(true);
404
405    resource->setLoadPriority(priority);
406    resource->load(this);
407
408    if (!inCache) {
409        resource->setOwningCachedResourceLoader(this);
410        resource->setInCache(false);
411    }
412
413    // We don't support immediate loads, but we do support immediate failure.
414    if (resource->errorOccurred()) {
415        if (inCache)
416            memoryCache()->remove(resource);
417        else
418            delete resource;
419        return 0;
420    }
421
422    m_validatedURLs.add(url.string());
423    return resource;
424}
425
426CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, bool forPreload, CachedResource* existingResource) const
427{
428    if (!existingResource)
429        return Load;
430
431    // We already have a preload going for this URL.
432    if (forPreload && existingResource->isPreloaded())
433        return Use;
434
435    // If the same URL has been loaded as a different type, we need to reload.
436    if (existingResource->type() != type) {
437        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch.");
438        return Reload;
439    }
440
441    // Don't reload resources while pasting.
442    if (m_allowStaleResources)
443        return Use;
444
445    // Alwaus use preloads.
446    if (existingResource->isPreloaded())
447        return Use;
448
449    // CachePolicyHistoryBuffer uses the cache no matter what.
450    if (cachePolicy() == CachePolicyHistoryBuffer)
451        return Use;
452
453    // Don't reuse resources with Cache-control: no-store.
454    if (existingResource->response().cacheControlContainsNoStore()) {
455        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store.");
456        return Reload;
457    }
458
459    // Avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to.
460    if (m_validatedURLs.contains(existingResource->url()))
461        return Use;
462
463    // CachePolicyReload always reloads
464    if (cachePolicy() == CachePolicyReload) {
465        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload.");
466        return Reload;
467    }
468
469    // We'll try to reload the resource if it failed last time.
470    if (existingResource->errorOccurred()) {
471        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state");
472        return Reload;
473    }
474
475    // For resources that are not yet loaded we ignore the cache policy.
476    if (existingResource->isLoading())
477        return Use;
478
479    // Check if the cache headers requires us to revalidate (cache expiration for example).
480    if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy())) {
481        // See if the resource has usable ETag or Last-modified headers.
482        if (existingResource->canUseCacheValidator())
483            return Revalidate;
484
485        // No, must reload.
486        LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators.");
487        return Reload;
488    }
489
490    return Use;
491}
492
493void CachedResourceLoader::printAccessDeniedMessage(const KURL& url) const
494{
495    if (url.isNull())
496        return;
497
498    if (!frame())
499        return;
500
501    Settings* settings = frame()->settings();
502    if (!settings || settings->privateBrowsingEnabled())
503        return;
504
505    String message = m_document->url().isNull() ?
506        makeString("Unsafe attempt to load URL ", url.string(), '.') :
507        makeString("Unsafe attempt to load URL ", url.string(), " from frame with URL ", m_document->url().string(), ". Domains, protocols and ports must match.\n");
508
509    // FIXME: provide a real line number and source URL.
510    frame()->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String());
511}
512
513void CachedResourceLoader::setAutoLoadImages(bool enable)
514{
515    if (enable == m_autoLoadImages)
516        return;
517
518    m_autoLoadImages = enable;
519
520    if (!m_autoLoadImages)
521        return;
522
523    DocumentResourceMap::iterator end = m_documentResources.end();
524    for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
525        CachedResource* resource = it->second.get();
526        if (resource->type() == CachedResource::ImageResource) {
527            CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource));
528#ifdef ANDROID_BLOCK_NETWORK_IMAGE
529            if (shouldBlockNetworkImage(image->url()))
530                continue;
531#endif
532            image->setAutoLoadWasPreventedBySettings(false);
533
534            if (image->stillNeedsLoad()) {
535                image->setLoading(true);
536                load(image, true);
537            }
538        }
539    }
540}
541
542#ifdef ANDROID_BLOCK_NETWORK_IMAGE
543bool CachedResourceLoader::shouldBlockNetworkImage(const String& url) const
544{
545    if (!m_blockNetworkImage)
546        return false;
547
548    KURL kurl = m_document->completeURL(url);
549    if (kurl.protocolIs("http") || kurl.protocolIs("https"))
550        return true;
551    return false;
552}
553
554void CachedResourceLoader::setBlockNetworkImage(bool block)
555{
556    if (block == m_blockNetworkImage)
557        return;
558
559    m_blockNetworkImage = block;
560
561    if (!m_autoLoadImages || m_blockNetworkImage)
562        return;
563
564    DocumentResourceMap::iterator end = m_documentResources.end();
565    for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) {
566        CachedResource* resource = it->second.get();
567        if (resource->type() == CachedResource::ImageResource) {
568            CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource));
569            image->setAutoLoadWasPreventedBySettings(false);
570            if (image->stillNeedsLoad()) {
571                image->setLoading(true);
572                load(image, true);
573            }
574        }
575    }
576}
577#endif
578
579CachePolicy CachedResourceLoader::cachePolicy() const
580{
581    return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify;
582}
583
584void CachedResourceLoader::removeCachedResource(CachedResource* resource) const
585{
586#ifndef NDEBUG
587    DocumentResourceMap::iterator it = m_documentResources.find(resource->url());
588    if (it != m_documentResources.end())
589        ASSERT(it->second.get() == resource);
590#endif
591    m_documentResources.remove(resource->url());
592}
593
594void CachedResourceLoader::load(CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks)
595{
596    incrementRequestCount(resource);
597
598    RefPtr<CachedResourceRequest> request = CachedResourceRequest::load(this, resource, incremental, securityCheck, sendResourceLoadCallbacks);
599    if (request)
600        m_requests.add(request);
601}
602
603void CachedResourceLoader::loadDone(CachedResourceRequest* request)
604{
605    m_loadFinishing = false;
606    RefPtr<CachedResourceRequest> protect(request);
607    if (request)
608        m_requests.remove(request);
609    if (frame())
610        frame()->loader()->loadDone();
611
612    if (!request) {
613        // If the request passed to this function is null, loadDone finished synchronously from when
614        // the load was started, so we want to kick off our next set of loads (via checkForPendingPreloads
615        // and servePendingRequests) asynchronously.
616        m_loadDoneActionTimer.startOneShot(0);
617        return;
618    }
619
620    performPostLoadActions();
621}
622
623void CachedResourceLoader::loadDoneActionTimerFired(Timer<CachedResourceLoader>*)
624{
625    performPostLoadActions();
626}
627
628void CachedResourceLoader::performPostLoadActions()
629{
630    checkForPendingPreloads();
631    resourceLoadScheduler()->servePendingRequests();
632}
633
634void CachedResourceLoader::cancelRequests()
635{
636    clearPendingPreloads();
637    Vector<CachedResourceRequest*, 256> requestsToCancel;
638    RequestSet::iterator end = m_requests.end();
639    for (RequestSet::iterator i = m_requests.begin(); i != end; ++i)
640        requestsToCancel.append((*i).get());
641
642    for (unsigned i = 0; i < requestsToCancel.size(); ++i)
643        requestsToCancel[i]->didFail(true);
644}
645
646void CachedResourceLoader::notifyLoadedFromMemoryCache(CachedResource* resource)
647{
648    if (!resource || !frame() || resource->status() != CachedResource::Cached)
649        return;
650
651    // FIXME: If the WebKit client changes or cancels the request, WebCore does not respect this and continues the load.
652    frame()->loader()->loadedResourceFromMemoryCache(resource);
653}
654
655void CachedResourceLoader::incrementRequestCount(const CachedResource* res)
656{
657    if (res->isLinkResource())
658        return;
659
660    ++m_requestCount;
661}
662
663void CachedResourceLoader::decrementRequestCount(const CachedResource* res)
664{
665    if (res->isLinkResource())
666        return;
667
668    --m_requestCount;
669    ASSERT(m_requestCount > -1);
670}
671
672int CachedResourceLoader::requestCount()
673{
674    if (m_loadFinishing)
675         return m_requestCount + 1;
676    return m_requestCount;
677}
678
679void CachedResourceLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody)
680{
681    // FIXME: Rip this out when we are sure it is no longer necessary (even for mobile).
682    UNUSED_PARAM(referencedFromBody);
683
684    bool hasRendering = m_document->body() && m_document->body()->renderer();
685    bool canBlockParser = type == CachedResource::Script || type == CachedResource::CSSStyleSheet;
686    if (!hasRendering && !canBlockParser) {
687        // Don't preload subresources that can't block the parser before we have something to draw.
688        // This helps prevent preloads from delaying first display when bandwidth is limited.
689        PendingPreload pendingPreload = { type, url, charset };
690        m_pendingPreloads.append(pendingPreload);
691        return;
692    }
693    requestPreload(type, url, charset);
694}
695
696void CachedResourceLoader::checkForPendingPreloads()
697{
698    if (m_pendingPreloads.isEmpty() || !m_document->body() || !m_document->body()->renderer())
699        return;
700    while (!m_pendingPreloads.isEmpty()) {
701        PendingPreload preload = m_pendingPreloads.takeFirst();
702        // 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).
703        if (!cachedResource(m_document->completeURL(preload.m_url)))
704            requestPreload(preload.m_type, preload.m_url, preload.m_charset);
705    }
706    m_pendingPreloads.clear();
707}
708
709void CachedResourceLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset)
710{
711    String encoding;
712    if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet)
713        encoding = charset.isEmpty() ? m_document->charset() : charset;
714
715    CachedResource* resource = requestResource(type, url, encoding, ResourceLoadPriorityUnresolved, true);
716    if (!resource || (m_preloads && m_preloads->contains(resource)))
717        return;
718    resource->increasePreloadCount();
719
720    if (!m_preloads)
721        m_preloads = adoptPtr(new ListHashSet<CachedResource*>);
722    m_preloads->add(resource);
723
724#if PRELOAD_DEBUG
725    printf("PRELOADING %s\n",  resource->url().latin1().data());
726#endif
727}
728
729void CachedResourceLoader::clearPreloads()
730{
731#if PRELOAD_DEBUG
732    printPreloadStats();
733#endif
734    if (!m_preloads)
735        return;
736
737    ListHashSet<CachedResource*>::iterator end = m_preloads->end();
738    for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) {
739        CachedResource* res = *it;
740        res->decreasePreloadCount();
741        if (res->canDelete() && !res->inCache())
742            delete res;
743        else if (res->preloadResult() == CachedResource::PreloadNotReferenced)
744            memoryCache()->remove(res);
745    }
746    m_preloads.clear();
747}
748
749void CachedResourceLoader::clearPendingPreloads()
750{
751    m_pendingPreloads.clear();
752}
753
754#if PRELOAD_DEBUG
755void CachedResourceLoader::printPreloadStats()
756{
757    unsigned scripts = 0;
758    unsigned scriptMisses = 0;
759    unsigned stylesheets = 0;
760    unsigned stylesheetMisses = 0;
761    unsigned images = 0;
762    unsigned imageMisses = 0;
763    ListHashSet<CachedResource*>::iterator end = m_preloads.end();
764    for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) {
765        CachedResource* res = *it;
766        if (res->preloadResult() == CachedResource::PreloadNotReferenced)
767            printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data());
768        else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete)
769            printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data());
770        else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading)
771            printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data());
772
773        if (res->type() == CachedResource::Script) {
774            scripts++;
775            if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
776                scriptMisses++;
777        } else if (res->type() == CachedResource::CSSStyleSheet) {
778            stylesheets++;
779            if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
780                stylesheetMisses++;
781        } else {
782            images++;
783            if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
784                imageMisses++;
785        }
786
787        if (res->errorOccurred())
788            memoryCache()->remove(res);
789
790        res->decreasePreloadCount();
791    }
792    m_preloads.clear();
793
794    if (scripts)
795        printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
796    if (stylesheets)
797        printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
798    if (images)
799        printf("IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
800}
801#endif
802
803}
804