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