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