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