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, 2007, 2008, 2009, 2010, 2011 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 "CachedSVGDocument.h" 32#include "CachedFont.h" 33#include "CachedImage.h" 34#include "CachedRawResource.h" 35#include "CachedResourceRequest.h" 36#include "CachedScript.h" 37#include "CachedXSLStyleSheet.h" 38#include "Console.h" 39#include "ContentSecurityPolicy.h" 40#include "DOMWindow.h" 41#include "Document.h" 42#include "DocumentLoader.h" 43#include "Frame.h" 44#include "FrameLoader.h" 45#include "FrameLoaderClient.h" 46#include "HTMLElement.h" 47#include "HTMLFrameOwnerElement.h" 48#include "LoaderStrategy.h" 49#include "Logging.h" 50#include "MemoryCache.h" 51#include "PingLoader.h" 52#include "PlatformStrategies.h" 53#include "ResourceLoadScheduler.h" 54#include "ScriptController.h" 55#include "SecurityOrigin.h" 56#include "Settings.h" 57#include <wtf/text/CString.h> 58#include <wtf/text/WTFString.h> 59 60#if ENABLE(VIDEO_TRACK) 61#include "CachedTextTrack.h" 62#endif 63 64#if ENABLE(CSS_SHADERS) 65#include "CachedShader.h" 66#endif 67 68#if ENABLE(RESOURCE_TIMING) 69#include "Performance.h" 70#endif 71 72#define PRELOAD_DEBUG 0 73 74namespace WebCore { 75 76static CachedResource* createResource(CachedResource::Type type, ResourceRequest& request, const String& charset) 77{ 78 switch (type) { 79 case CachedResource::ImageResource: 80 return new CachedImage(request); 81 case CachedResource::CSSStyleSheet: 82 return new CachedCSSStyleSheet(request, charset); 83 case CachedResource::Script: 84 return new CachedScript(request, charset); 85#if ENABLE(SVG) 86 case CachedResource::SVGDocumentResource: 87 return new CachedSVGDocument(request); 88#endif 89 case CachedResource::FontResource: 90 return new CachedFont(request); 91 case CachedResource::RawResource: 92 case CachedResource::MainResource: 93 return new CachedRawResource(request, type); 94#if ENABLE(XSLT) 95 case CachedResource::XSLStyleSheet: 96 return new CachedXSLStyleSheet(request); 97#endif 98#if ENABLE(LINK_PREFETCH) 99 case CachedResource::LinkPrefetch: 100 return new CachedResource(request, CachedResource::LinkPrefetch); 101 case CachedResource::LinkSubresource: 102 return new CachedResource(request, CachedResource::LinkSubresource); 103#endif 104#if ENABLE(VIDEO_TRACK) 105 case CachedResource::TextTrackResource: 106 return new CachedTextTrack(request); 107#endif 108#if ENABLE(CSS_SHADERS) 109 case CachedResource::ShaderResource: 110 return new CachedShader(request); 111#endif 112 } 113 ASSERT_NOT_REACHED(); 114 return 0; 115} 116 117CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader) 118 : m_document(0) 119 , m_documentLoader(documentLoader) 120 , m_requestCount(0) 121 , m_garbageCollectDocumentResourcesTimer(this, &CachedResourceLoader::garbageCollectDocumentResourcesTimerFired) 122 , m_autoLoadImages(true) 123 , m_imagesEnabled(true) 124 , m_allowStaleResources(false) 125{ 126} 127 128CachedResourceLoader::~CachedResourceLoader() 129{ 130 m_documentLoader = 0; 131 m_document = 0; 132 133 clearPreloads(); 134 DocumentResourceMap::iterator end = m_documentResources.end(); 135 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) 136 it->value->setOwningCachedResourceLoader(0); 137 138 // Make sure no requests still point to this CachedResourceLoader 139 ASSERT(m_requestCount == 0); 140} 141 142CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const 143{ 144 KURL url = m_document->completeURL(resourceURL); 145 return cachedResource(url); 146} 147 148CachedResource* CachedResourceLoader::cachedResource(const KURL& resourceURL) const 149{ 150 KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL); 151 return m_documentResources.get(url).get(); 152} 153 154Frame* CachedResourceLoader::frame() const 155{ 156 return m_documentLoader ? m_documentLoader->frame() : 0; 157} 158 159CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest& request) 160{ 161 if (Frame* f = frame()) { 162 if (f->loader()->pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) { 163 KURL requestURL = request.resourceRequest().url(); 164 if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL)) 165 PingLoader::loadImage(f, requestURL); 166 return 0; 167 } 168 } 169 request.setDefer(clientDefersImage(request.resourceRequest().url()) ? CachedResourceRequest::DeferredByClient : CachedResourceRequest::NoDefer); 170 return static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, request).get()); 171} 172 173CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest& request) 174{ 175 return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, request).get()); 176} 177 178#if ENABLE(VIDEO_TRACK) 179CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest& request) 180{ 181 return static_cast<CachedTextTrack*>(requestResource(CachedResource::TextTrackResource, request).get()); 182} 183#endif 184 185#if ENABLE(CSS_SHADERS) 186CachedResourceHandle<CachedShader> CachedResourceLoader::requestShader(CachedResourceRequest& request) 187{ 188 return static_cast<CachedShader*>(requestResource(CachedResource::ShaderResource, request).get()); 189} 190#endif 191 192CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest& request) 193{ 194 return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, request).get()); 195} 196 197CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest& request) 198{ 199 KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(request.resourceRequest().url()); 200 201#if ENABLE(CACHE_PARTITIONING) 202 request.mutableResourceRequest().setCachePartition(document()->topOrigin()->cachePartition()); 203#endif 204 205 if (CachedResource* existing = memoryCache()->resourceForRequest(request.resourceRequest())) { 206 if (existing->type() == CachedResource::CSSStyleSheet) 207 return static_cast<CachedCSSStyleSheet*>(existing); 208 memoryCache()->remove(existing); 209 } 210 if (url.string() != request.resourceRequest().url()) 211 request.mutableResourceRequest().setURL(url); 212 213 CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(request.resourceRequest(), request.charset()); 214 215 memoryCache()->add(userSheet.get()); 216 // FIXME: loadResource calls setOwningCachedResourceLoader() if the resource couldn't be added to cache. Does this function need to call it, too? 217 218 userSheet->load(this, ResourceLoaderOptions(DoNotSendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, SkipSecurityCheck)); 219 220 return userSheet; 221} 222 223CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest& request) 224{ 225 return static_cast<CachedScript*>(requestResource(CachedResource::Script, request).get()); 226} 227 228#if ENABLE(XSLT) 229CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest& request) 230{ 231 return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, request).get()); 232} 233#endif 234 235#if ENABLE(SVG) 236CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest& request) 237{ 238 return static_cast<CachedSVGDocument*>(requestResource(CachedResource::SVGDocumentResource, request).get()); 239} 240#endif 241 242#if ENABLE(LINK_PREFETCH) 243CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest& request) 244{ 245 ASSERT(frame()); 246 ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource); 247 return requestResource(type, request); 248} 249#endif 250 251CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest& request) 252{ 253 return static_cast<CachedRawResource*>(requestResource(CachedResource::RawResource, request).get()); 254} 255 256CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest& request) 257{ 258 return static_cast<CachedRawResource*>(requestResource(CachedResource::MainResource, request).get()); 259} 260 261bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const KURL& url) const 262{ 263 switch (type) { 264 case CachedResource::Script: 265#if ENABLE(XSLT) 266 case CachedResource::XSLStyleSheet: 267#endif 268#if ENABLE(SVG) 269 case CachedResource::SVGDocumentResource: 270#endif 271 case CachedResource::CSSStyleSheet: 272 // These resource can inject script into the current document (Script, 273 // XSL) or exfiltrate the content of the current document (CSS). 274 if (Frame* f = frame()) 275 if (!f->loader()->mixedContentChecker()->canRunInsecureContent(m_document->securityOrigin(), url)) 276 return false; 277 break; 278#if ENABLE(VIDEO_TRACK) 279 case CachedResource::TextTrackResource: 280#endif 281#if ENABLE(CSS_SHADERS) 282 case CachedResource::ShaderResource: 283#endif 284 case CachedResource::RawResource: 285 case CachedResource::ImageResource: 286 case CachedResource::FontResource: { 287 // These resources can corrupt only the frame's pixels. 288 if (Frame* f = frame()) { 289 Frame* top = f->tree()->top(); 290 if (!top->loader()->mixedContentChecker()->canDisplayInsecureContent(top->document()->securityOrigin(), url)) 291 return false; 292 } 293 break; 294 } 295 case CachedResource::MainResource: 296#if ENABLE(LINK_PREFETCH) 297 case CachedResource::LinkPrefetch: 298 case CachedResource::LinkSubresource: 299 // Prefetch cannot affect the current document. 300#endif 301 break; 302 } 303 return true; 304} 305 306bool CachedResourceLoader::canRequest(CachedResource::Type type, const KURL& url, bool forPreload) 307{ 308 if (document() && !document()->securityOrigin()->canDisplay(url)) { 309 if (!forPreload) 310 FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); 311 LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); 312 return 0; 313 } 314 315 // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. 316 bool shouldBypassMainWorldContentSecurityPolicy = (frame() && frame()->script()->shouldBypassMainWorldContentSecurityPolicy()); 317 318 // Some types of resources can be loaded only from the same origin. Other 319 // types of resources, like Images, Scripts, and CSS, can be loaded from 320 // any URL. 321 switch (type) { 322 case CachedResource::MainResource: 323 case CachedResource::ImageResource: 324 case CachedResource::CSSStyleSheet: 325 case CachedResource::Script: 326 case CachedResource::FontResource: 327 case CachedResource::RawResource: 328#if ENABLE(LINK_PREFETCH) 329 case CachedResource::LinkPrefetch: 330 case CachedResource::LinkSubresource: 331#endif 332#if ENABLE(VIDEO_TRACK) 333 case CachedResource::TextTrackResource: 334#endif 335#if ENABLE(CSS_SHADERS) 336 case CachedResource::ShaderResource: 337#endif 338 // These types of resources can be loaded from any origin. 339 // FIXME: Are we sure about CachedResource::FontResource? 340 break; 341#if ENABLE(SVG) 342 case CachedResource::SVGDocumentResource: 343#endif 344#if ENABLE(XSLT) 345 case CachedResource::XSLStyleSheet: 346 if (!m_document->securityOrigin()->canRequest(url)) { 347 printAccessDeniedMessage(url); 348 return false; 349 } 350#endif 351 break; 352 } 353 354 switch (type) { 355#if ENABLE(XSLT) 356 case CachedResource::XSLStyleSheet: 357 if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url)) 358 return false; 359 break; 360#endif 361 case CachedResource::Script: 362 if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url)) 363 return false; 364 365 if (frame()) { 366 Settings* settings = frame()->settings(); 367 if (!frame()->loader()->client()->allowScriptFromSource(!settings || settings->isScriptEnabled(), url)) { 368 frame()->loader()->client()->didNotAllowScript(); 369 return false; 370 } 371 } 372 break; 373#if ENABLE(CSS_SHADERS) 374 case CachedResource::ShaderResource: 375 // Since shaders are referenced from CSS Styles use the same rules here. 376#endif 377 case CachedResource::CSSStyleSheet: 378 if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowStyleFromSource(url)) 379 return false; 380 break; 381#if ENABLE(SVG) 382 case CachedResource::SVGDocumentResource: 383#endif 384 case CachedResource::ImageResource: 385 if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowImageFromSource(url)) 386 return false; 387 break; 388 case CachedResource::FontResource: { 389 if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowFontFromSource(url)) 390 return false; 391 break; 392 } 393 case CachedResource::MainResource: 394 case CachedResource::RawResource: 395#if ENABLE(LINK_PREFETCH) 396 case CachedResource::LinkPrefetch: 397 case CachedResource::LinkSubresource: 398#endif 399 break; 400#if ENABLE(VIDEO_TRACK) 401 case CachedResource::TextTrackResource: 402 // Cues aren't called out in the CPS spec yet, but they only work with a media element 403 // so use the media policy. 404 if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowMediaFromSource(url)) 405 return false; 406 break; 407#endif 408 } 409 410 // Last of all, check for insecure content. We do this last so that when 411 // folks block insecure content with a CSP policy, they don't get a warning. 412 // They'll still get a warning in the console about CSP blocking the load. 413 414 // FIXME: Should we consider forPreload here? 415 if (!checkInsecureContent(type, url)) 416 return false; 417 418 return true; 419} 420 421bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource* resource) 422{ 423 if (!resource || !frame() || resource->status() != CachedResource::Cached) 424 return true; 425 426 ResourceRequest newRequest = ResourceRequest(resource->url()); 427#if ENABLE(INSPECTOR) 428 if (request.resourceRequest().hiddenFromInspector()) 429 newRequest.setHiddenFromInspector(true); 430#endif 431 frame()->loader()->loadedResourceFromMemoryCache(resource, newRequest); 432 433 // FIXME <http://webkit.org/b/113251>: If the delegate modifies the request's 434 // URL, it is no longer appropriate to use this CachedResource. 435 return !newRequest.isNull(); 436} 437 438CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request) 439{ 440 KURL url = request.resourceRequest().url(); 441 442 LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload()); 443 444 // If only the fragment identifiers differ, it is the same resource. 445 url = MemoryCache::removeFragmentIdentifierIfNeeded(url); 446 447 if (!url.isValid()) 448 return 0; 449 450 if (!canRequest(type, url, request.forPreload())) 451 return 0; 452 453 if (Frame* f = frame()) 454 f->loader()->client()->dispatchWillRequestResource(&request); 455 456 if (memoryCache()->disabled()) { 457 DocumentResourceMap::iterator it = m_documentResources.find(url.string()); 458 if (it != m_documentResources.end()) { 459 it->value->setOwningCachedResourceLoader(0); 460 m_documentResources.remove(it); 461 } 462 } 463 464 // See if we can use an existing resource from the cache. 465 CachedResourceHandle<CachedResource> resource; 466#if ENABLE(CACHE_PARTITIONING) 467 if (document()) 468 request.mutableResourceRequest().setCachePartition(document()->topOrigin()->cachePartition()); 469#endif 470 471 resource = memoryCache()->resourceForRequest(request.resourceRequest()); 472 473 const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer()); 474 switch (policy) { 475 case Reload: 476 memoryCache()->remove(resource.get()); 477 // Fall through 478 case Load: 479 resource = loadResource(type, request, request.charset()); 480 break; 481 case Revalidate: 482 resource = revalidateResource(request, resource.get()); 483 break; 484 case Use: 485 if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, resource.get())) 486 return 0; 487 memoryCache()->resourceAccessed(resource.get()); 488 break; 489 } 490 491 if (!resource) 492 return 0; 493 494 if (!request.forPreload() || policy != Use) 495 resource->setLoadPriority(request.priority()); 496 497 if ((policy != Use || resource->stillNeedsLoad()) && CachedResourceRequest::NoDefer == request.defer()) { 498 resource->load(this, request.options()); 499 500 // We don't support immediate loads, but we do support immediate failure. 501 if (resource->errorOccurred()) { 502 if (resource->inCache()) 503 memoryCache()->remove(resource.get()); 504 return 0; 505 } 506 } 507 508 if (!request.resourceRequest().url().protocolIsData()) 509 m_validatedURLs.add(request.resourceRequest().url()); 510 511 ASSERT(resource->url() == url.string()); 512 m_documentResources.set(resource->url(), resource); 513 return resource; 514} 515 516CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(const CachedResourceRequest& request, CachedResource* resource) 517{ 518 ASSERT(resource); 519 ASSERT(resource->inCache()); 520 ASSERT(!memoryCache()->disabled()); 521 ASSERT(resource->canUseCacheValidator()); 522 ASSERT(!resource->resourceToRevalidate()); 523 524 // Copy the URL out of the resource to be revalidated in case it gets deleted by the remove() call below. 525 String url = resource->url(); 526 CachedResourceHandle<CachedResource> newResource = createResource(resource->type(), resource->resourceRequest(), resource->encoding()); 527 528 LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource); 529 newResource->setResourceToRevalidate(resource); 530 531 memoryCache()->remove(resource); 532 memoryCache()->add(newResource.get()); 533#if ENABLE(RESOURCE_TIMING) 534 storeResourceTimingInitiatorInformation(resource, request); 535#else 536 UNUSED_PARAM(request); 537#endif 538 return newResource; 539} 540 541CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest& request, const String& charset) 542{ 543 ASSERT(!memoryCache()->resourceForRequest(request.resourceRequest())); 544 545 LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data()); 546 547 CachedResourceHandle<CachedResource> resource = createResource(type, request.mutableResourceRequest(), charset); 548 549 if (!memoryCache()->add(resource.get())) 550 resource->setOwningCachedResourceLoader(this); 551#if ENABLE(RESOURCE_TIMING) 552 storeResourceTimingInitiatorInformation(resource, request); 553#endif 554 return resource; 555} 556 557#if ENABLE(RESOURCE_TIMING) 558void CachedResourceLoader::storeResourceTimingInitiatorInformation(const CachedResourceHandle<CachedResource>& resource, const CachedResourceRequest& request) 559{ 560 if (resource->type() == CachedResource::MainResource) { 561 // <iframe>s should report the initial navigation requested by the parent document, but not subsequent navigations. 562 if (frame()->ownerElement() && m_documentLoader->frameLoader()->stateMachine()->committingFirstRealLoad()) { 563 InitiatorInfo info = { frame()->ownerElement()->localName(), monotonicallyIncreasingTime() }; 564 m_initiatorMap.add(resource.get(), info); 565 } 566 } else { 567 InitiatorInfo info = { request.initiatorName(), monotonicallyIncreasingTime() }; 568 m_initiatorMap.add(resource.get(), info); 569 } 570} 571#endif // ENABLE(RESOURCE_TIMING) 572 573CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, ResourceRequest& request, bool forPreload, CachedResource* existingResource, CachedResourceRequest::DeferOption defer) const 574{ 575 if (!existingResource) 576 return Load; 577 578 // We already have a preload going for this URL. 579 if (forPreload && existingResource->isPreloaded()) 580 return Use; 581 582 // If the same URL has been loaded as a different type, we need to reload. 583 if (existingResource->type() != type) { 584 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch."); 585 return Reload; 586 } 587 588 if (!existingResource->canReuse(request)) 589 return Reload; 590 591 // Certain requests (e.g., XHRs) might have manually set headers that require revalidation. 592 // FIXME: In theory, this should be a Revalidate case. In practice, the MemoryCache revalidation path assumes a whole bunch 593 // of things about how revalidation works that manual headers violate, so punt to Reload instead. 594 if (request.isConditional()) 595 return Reload; 596 597 // Do not load from cache if images are not enabled. The load for this image will be blocked 598 // in CachedImage::load. 599 if (CachedResourceRequest::DeferredByClient == defer) 600 return Reload; 601 602 // Don't reload resources while pasting. 603 if (m_allowStaleResources) 604 return Use; 605 606 // Alwaus use preloads. 607 if (existingResource->isPreloaded()) 608 return Use; 609 610 // CachePolicyHistoryBuffer uses the cache no matter what. 611 if (cachePolicy(type) == CachePolicyHistoryBuffer) 612 return Use; 613 614 // Don't reuse resources with Cache-control: no-store. 615 if (existingResource->response().cacheControlContainsNoStore()) { 616 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store."); 617 return Reload; 618 } 619 620 // If credentials were sent with the previous request and won't be 621 // with this one, or vice versa, re-fetch the resource. 622 // 623 // This helps with the case where the server sends back 624 // "Access-Control-Allow-Origin: *" all the time, but some of the 625 // client's requests are made without CORS and some with. 626 if (existingResource->resourceRequest().allowCookies() != request.allowCookies()) { 627 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to difference in credentials settings."); 628 return Reload; 629 } 630 631 // During the initial load, avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to. 632 if (document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url())) 633 return Use; 634 635 // CachePolicyReload always reloads 636 if (cachePolicy(type) == CachePolicyReload) { 637 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload."); 638 return Reload; 639 } 640 641 // We'll try to reload the resource if it failed last time. 642 if (existingResource->errorOccurred()) { 643 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state"); 644 return Reload; 645 } 646 647 // For resources that are not yet loaded we ignore the cache policy. 648 if (existingResource->isLoading()) 649 return Use; 650 651 // Check if the cache headers requires us to revalidate (cache expiration for example). 652 if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy(type))) { 653 // See if the resource has usable ETag or Last-modified headers. 654 if (existingResource->canUseCacheValidator()) 655 return Revalidate; 656 657 // No, must reload. 658 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators."); 659 return Reload; 660 } 661 662 return Use; 663} 664 665void CachedResourceLoader::printAccessDeniedMessage(const KURL& url) const 666{ 667 if (url.isNull()) 668 return; 669 670 if (!frame()) 671 return; 672 673 String message; 674 if (!m_document || m_document->url().isNull()) 675 message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + '.'; 676 else 677 message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from frame with URL " + m_document->url().stringCenterEllipsizedToLength() + ". Domains, protocols and ports must match.\n"; 678 679 frame()->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); 680} 681 682void CachedResourceLoader::setAutoLoadImages(bool enable) 683{ 684 if (enable == m_autoLoadImages) 685 return; 686 687 m_autoLoadImages = enable; 688 689 if (!m_autoLoadImages) 690 return; 691 692 reloadImagesIfNotDeferred(); 693} 694 695void CachedResourceLoader::setImagesEnabled(bool enable) 696{ 697 if (enable == m_imagesEnabled) 698 return; 699 700 m_imagesEnabled = enable; 701 702 if (!m_imagesEnabled) 703 return; 704 705 reloadImagesIfNotDeferred(); 706} 707 708bool CachedResourceLoader::clientDefersImage(const KURL& url) const 709{ 710 return frame() && !frame()->loader()->client()->allowImage(m_imagesEnabled, url); 711} 712 713bool CachedResourceLoader::shouldDeferImageLoad(const KURL& url) const 714{ 715 return clientDefersImage(url) || !m_autoLoadImages; 716} 717 718void CachedResourceLoader::reloadImagesIfNotDeferred() 719{ 720 DocumentResourceMap::iterator end = m_documentResources.end(); 721 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { 722 CachedResource* resource = it->value.get(); 723 if (resource->type() == CachedResource::ImageResource && resource->stillNeedsLoad() && !clientDefersImage(resource->url())) 724 const_cast<CachedResource*>(resource)->load(this, defaultCachedResourceOptions()); 725 } 726} 727 728CachePolicy CachedResourceLoader::cachePolicy(CachedResource::Type type) const 729{ 730 if (!frame()) 731 return CachePolicyVerify; 732 733 if (type != CachedResource::MainResource) 734 return frame()->loader()->subresourceCachePolicy(); 735 736 if (frame()->loader()->loadType() == FrameLoadTypeReloadFromOrigin || frame()->loader()->loadType() == FrameLoadTypeReload) 737 return CachePolicyReload; 738 return CachePolicyVerify; 739} 740 741void CachedResourceLoader::removeCachedResource(CachedResource* resource) const 742{ 743#ifndef NDEBUG 744 DocumentResourceMap::iterator it = m_documentResources.find(resource->url()); 745 if (it != m_documentResources.end()) 746 ASSERT(it->value.get() == resource); 747#endif 748 m_documentResources.remove(resource->url()); 749} 750 751void CachedResourceLoader::loadDone(CachedResource* resource) 752{ 753 RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader); 754 RefPtr<Document> protectDocument(m_document); 755 756#if ENABLE(RESOURCE_TIMING) 757 if (resource && resource->response().isHTTP() && ((!resource->errorOccurred() && !resource->wasCanceled()) || resource->response().httpStatusCode() == 304)) { 758 HashMap<CachedResource*, InitiatorInfo>::iterator initiatorIt = m_initiatorMap.find(resource); 759 if (initiatorIt != m_initiatorMap.end()) { 760 ASSERT(document()); 761 Document* initiatorDocument = document(); 762 if (resource->type() == CachedResource::MainResource) 763 initiatorDocument = document()->parentDocument(); 764 ASSERT(initiatorDocument); 765 const InitiatorInfo& info = initiatorIt->value; 766 initiatorDocument->domWindow()->performance()->addResourceTiming(info.name, initiatorDocument, resource->resourceRequest(), resource->response(), info.startTime, resource->loadFinishTime()); 767 m_initiatorMap.remove(initiatorIt); 768 } 769 } 770#else 771 UNUSED_PARAM(resource); 772#endif // ENABLE(RESOURCE_TIMING) 773 774 if (frame()) 775 frame()->loader()->loadDone(); 776 performPostLoadActions(); 777 778 if (!m_garbageCollectDocumentResourcesTimer.isActive()) 779 m_garbageCollectDocumentResourcesTimer.startOneShot(0); 780} 781 782// Garbage collecting m_documentResources is a workaround for the 783// CachedResourceHandles on the RHS being strong references. Ideally this 784// would be a weak map, however CachedResourceHandles perform additional 785// bookkeeping on CachedResources, so instead pseudo-GC them -- when the 786// reference count reaches 1, m_documentResources is the only reference, so 787// remove it from the map. 788void CachedResourceLoader::garbageCollectDocumentResourcesTimerFired(Timer<CachedResourceLoader>* timer) 789{ 790 ASSERT_UNUSED(timer, timer == &m_garbageCollectDocumentResourcesTimer); 791 garbageCollectDocumentResources(); 792} 793 794void CachedResourceLoader::garbageCollectDocumentResources() 795{ 796 typedef Vector<String, 10> StringVector; 797 StringVector resourcesToDelete; 798 799 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != m_documentResources.end(); ++it) { 800 if (it->value->hasOneHandle()) { 801 resourcesToDelete.append(it->key); 802 it->value->setOwningCachedResourceLoader(0); 803 } 804 } 805 806 for (StringVector::const_iterator it = resourcesToDelete.begin(); it != resourcesToDelete.end(); ++it) 807 m_documentResources.remove(*it); 808} 809 810void CachedResourceLoader::performPostLoadActions() 811{ 812 checkForPendingPreloads(); 813 814 platformStrategies()->loaderStrategy()->resourceLoadScheduler()->servePendingRequests(); 815} 816 817void CachedResourceLoader::incrementRequestCount(const CachedResource* res) 818{ 819 if (res->ignoreForRequestCount()) 820 return; 821 822 ++m_requestCount; 823} 824 825void CachedResourceLoader::decrementRequestCount(const CachedResource* res) 826{ 827 if (res->ignoreForRequestCount()) 828 return; 829 830 --m_requestCount; 831 ASSERT(m_requestCount > -1); 832} 833 834void CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest& request, const String& charset) 835{ 836 bool delaySubresourceLoad = true; 837#if PLATFORM(IOS) 838 delaySubresourceLoad = false; 839#endif 840 if (delaySubresourceLoad) { 841 bool hasRendering = m_document->body() && m_document->body()->renderer(); 842 bool canBlockParser = type == CachedResource::Script || type == CachedResource::CSSStyleSheet; 843 if (!hasRendering && !canBlockParser) { 844 // Don't preload subresources that can't block the parser before we have something to draw. 845 // This helps prevent preloads from delaying first display when bandwidth is limited. 846 PendingPreload pendingPreload = { type, request, charset }; 847 m_pendingPreloads.append(pendingPreload); 848 return; 849 } 850 } 851 requestPreload(type, request, charset); 852} 853 854void CachedResourceLoader::checkForPendingPreloads() 855{ 856 if (m_pendingPreloads.isEmpty() || !m_document->body() || !m_document->body()->renderer()) 857 return; 858 while (!m_pendingPreloads.isEmpty()) { 859 PendingPreload preload = m_pendingPreloads.takeFirst(); 860 // 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). 861 if (!cachedResource(preload.m_request.resourceRequest().url())) 862 requestPreload(preload.m_type, preload.m_request, preload.m_charset); 863 } 864 m_pendingPreloads.clear(); 865} 866 867void CachedResourceLoader::requestPreload(CachedResource::Type type, CachedResourceRequest& request, const String& charset) 868{ 869 String encoding; 870 if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) 871 encoding = charset.isEmpty() ? m_document->charset() : charset; 872 873 request.setCharset(encoding); 874 request.setForPreload(true); 875 876 CachedResourceHandle<CachedResource> resource = requestResource(type, request); 877 if (!resource || (m_preloads && m_preloads->contains(resource.get()))) 878 return; 879 resource->increasePreloadCount(); 880 881 if (!m_preloads) 882 m_preloads = adoptPtr(new ListHashSet<CachedResource*>); 883 m_preloads->add(resource.get()); 884 885#if PRELOAD_DEBUG 886 printf("PRELOADING %s\n", resource->url().latin1().data()); 887#endif 888} 889 890bool CachedResourceLoader::isPreloaded(const String& urlString) const 891{ 892 const KURL& url = m_document->completeURL(urlString); 893 894 if (m_preloads) { 895 ListHashSet<CachedResource*>::iterator end = m_preloads->end(); 896 for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) { 897 CachedResource* resource = *it; 898 if (resource->url() == url) 899 return true; 900 } 901 } 902 903 Deque<PendingPreload>::const_iterator dequeEnd = m_pendingPreloads.end(); 904 for (Deque<PendingPreload>::const_iterator it = m_pendingPreloads.begin(); it != dequeEnd; ++it) { 905 PendingPreload pendingPreload = *it; 906 if (pendingPreload.m_request.resourceRequest().url() == url) 907 return true; 908 } 909 return false; 910} 911 912void CachedResourceLoader::clearPreloads() 913{ 914#if PRELOAD_DEBUG 915 printPreloadStats(); 916#endif 917 if (!m_preloads) 918 return; 919 920 ListHashSet<CachedResource*>::iterator end = m_preloads->end(); 921 for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) { 922 CachedResource* res = *it; 923 res->decreasePreloadCount(); 924 bool deleted = res->deleteIfPossible(); 925 if (!deleted && res->preloadResult() == CachedResource::PreloadNotReferenced) 926 memoryCache()->remove(res); 927 } 928 m_preloads.clear(); 929} 930 931void CachedResourceLoader::clearPendingPreloads() 932{ 933 m_pendingPreloads.clear(); 934} 935 936#if PRELOAD_DEBUG 937void CachedResourceLoader::printPreloadStats() 938{ 939 unsigned scripts = 0; 940 unsigned scriptMisses = 0; 941 unsigned stylesheets = 0; 942 unsigned stylesheetMisses = 0; 943 unsigned images = 0; 944 unsigned imageMisses = 0; 945 ListHashSet<CachedResource*>::iterator end = m_preloads.end(); 946 for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) { 947 CachedResource* res = *it; 948 if (res->preloadResult() == CachedResource::PreloadNotReferenced) 949 printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data()); 950 else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete) 951 printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data()); 952 else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading) 953 printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data()); 954 955 if (res->type() == CachedResource::Script) { 956 scripts++; 957 if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) 958 scriptMisses++; 959 } else if (res->type() == CachedResource::CSSStyleSheet) { 960 stylesheets++; 961 if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) 962 stylesheetMisses++; 963 } else { 964 images++; 965 if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) 966 imageMisses++; 967 } 968 969 if (res->errorOccurred()) 970 memoryCache()->remove(res); 971 972 res->decreasePreloadCount(); 973 } 974 m_preloads.clear(); 975 976 if (scripts) 977 printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts); 978 if (stylesheets) 979 printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets); 980 if (images) 981 printf("IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images); 982} 983#endif 984 985const ResourceLoaderOptions& CachedResourceLoader::defaultCachedResourceOptions() 986{ 987 static ResourceLoaderOptions options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, DoSecurityCheck); 988 return options; 989} 990 991} 992