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