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 }