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 (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 160 CachedFont* CachedResourceLoader::requestFont(const String& url) 161 { 162 return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String())); 163 } 164 165 CachedCSSStyleSheet* CachedResourceLoader::requestCSSStyleSheet(const String& url, const String& charset, ResourceLoadPriority priority) 166 { 167 return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset, priority)); 168 } 169 170 CachedCSSStyleSheet* 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 193 CachedScript* 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) 199 CachedXSLStyleSheet* 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) 206 CachedResource* CachedResourceLoader::requestLinkResource(const String& url, ResourceLoadPriority priority) 207 { 208 ASSERT(frame()); 209 return requestResource(CachedResource::LinkResource, url, String(), priority); 210 } 211 #endif 212 213 bool 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 305 CachedResource* 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 365 CachedResource* 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 390 CachedResource* 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 426 CachedResourceLoader::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 493 void 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 513 void 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 543 bool 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 554 void 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 579 CachePolicy CachedResourceLoader::cachePolicy() const 580 { 581 return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify; 582 } 583 584 void 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 594 void 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 603 void 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 623 void CachedResourceLoader::loadDoneActionTimerFired(Timer<CachedResourceLoader>*) 624 { 625 performPostLoadActions(); 626 } 627 628 void CachedResourceLoader::performPostLoadActions() 629 { 630 checkForPendingPreloads(); 631 resourceLoadScheduler()->servePendingRequests(); 632 } 633 634 void 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 646 void 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 655 void CachedResourceLoader::incrementRequestCount(const CachedResource* res) 656 { 657 if (res->isLinkResource()) 658 return; 659 660 ++m_requestCount; 661 } 662 663 void CachedResourceLoader::decrementRequestCount(const CachedResource* res) 664 { 665 if (res->isLinkResource()) 666 return; 667 668 --m_requestCount; 669 ASSERT(m_requestCount > -1); 670 } 671 672 int CachedResourceLoader::requestCount() 673 { 674 if (m_loadFinishing) 675 return m_requestCount + 1; 676 return m_requestCount; 677 } 678 679 void 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 696 void 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 709 void 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 729 void 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 749 void CachedResourceLoader::clearPendingPreloads() 750 { 751 m_pendingPreloads.clear(); 752 } 753 754 #if PRELOAD_DEBUG 755 void 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