Make CachedResourceLoader originsMatch check more efficient
[WebKit-https.git] / Source / WebCore / loader / cache / CachedResourceLoader.cpp
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-2016 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 "CachedSVGFont.h"
37 #include "CachedScript.h"
38 #include "CachedXSLStyleSheet.h"
39 #include "Chrome.h"
40 #include "ChromeClient.h"
41 #include "ContentExtensionError.h"
42 #include "ContentExtensionRule.h"
43 #include "ContentSecurityPolicy.h"
44 #include "DOMWindow.h"
45 #include "DiagnosticLoggingClient.h"
46 #include "DiagnosticLoggingKeys.h"
47 #include "Document.h"
48 #include "DocumentLoader.h"
49 #include "Frame.h"
50 #include "FrameLoader.h"
51 #include "FrameLoaderClient.h"
52 #include "HTMLElement.h"
53 #include "HTMLFrameOwnerElement.h"
54 #include "LoaderStrategy.h"
55 #include "LocalizedStrings.h"
56 #include "Logging.h"
57 #include "MainFrame.h"
58 #include "MemoryCache.h"
59 #include "Page.h"
60 #include "Performance.h"
61 #include "PingLoader.h"
62 #include "PlatformStrategies.h"
63 #include "RenderElement.h"
64 #include "ResourceLoadInfo.h"
65 #include "RuntimeEnabledFeatures.h"
66 #include "ScriptController.h"
67 #include "SecurityOrigin.h"
68 #include "SecurityPolicy.h"
69 #include "SessionID.h"
70 #include "Settings.h"
71 #include "StyleSheetContents.h"
72 #include "SubresourceLoader.h"
73 #include "UserContentController.h"
74 #include "UserStyleSheet.h"
75 #include <wtf/text/CString.h>
76 #include <wtf/text/WTFString.h>
77
78 #if ENABLE(VIDEO_TRACK)
79 #include "CachedTextTrack.h"
80 #endif
81
82 #define PRELOAD_DEBUG 0
83
84 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), Network, "%p - CachedResourceLoader::" fmt, this, ##__VA_ARGS__)
85
86 namespace WebCore {
87
88 static CachedResource* createResource(CachedResource::Type type, CachedResourceRequest&& request, SessionID sessionID)
89 {
90     switch (type) {
91     case CachedResource::ImageResource:
92         return new CachedImage(WTFMove(request), sessionID);
93     case CachedResource::CSSStyleSheet:
94         return new CachedCSSStyleSheet(WTFMove(request), sessionID);
95     case CachedResource::Script:
96         return new CachedScript(WTFMove(request), sessionID);
97     case CachedResource::SVGDocumentResource:
98         return new CachedSVGDocument(WTFMove(request), sessionID);
99 #if ENABLE(SVG_FONTS)
100     case CachedResource::SVGFontResource:
101         return new CachedSVGFont(WTFMove(request), sessionID);
102 #endif
103     case CachedResource::FontResource:
104         return new CachedFont(WTFMove(request), sessionID);
105     case CachedResource::MediaResource:
106     case CachedResource::RawResource:
107     case CachedResource::MainResource:
108         return new CachedRawResource(WTFMove(request), type, sessionID);
109 #if ENABLE(XSLT)
110     case CachedResource::XSLStyleSheet:
111         return new CachedXSLStyleSheet(WTFMove(request), sessionID);
112 #endif
113 #if ENABLE(LINK_PREFETCH)
114     case CachedResource::LinkPrefetch:
115         return new CachedResource(WTFMove(request), CachedResource::LinkPrefetch, sessionID);
116     case CachedResource::LinkSubresource:
117         return new CachedResource(WTFMove(request), CachedResource::LinkSubresource, sessionID);
118 #endif
119 #if ENABLE(VIDEO_TRACK)
120     case CachedResource::TextTrackResource:
121         return new CachedTextTrack(WTFMove(request), sessionID);
122 #endif
123     }
124     ASSERT_NOT_REACHED();
125     return nullptr;
126 }
127
128 CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader)
129     : m_document(nullptr)
130     , m_documentLoader(documentLoader)
131     , m_requestCount(0)
132     , m_garbageCollectDocumentResourcesTimer(*this, &CachedResourceLoader::garbageCollectDocumentResources)
133     , m_autoLoadImages(true)
134     , m_imagesEnabled(true)
135     , m_allowStaleResources(false)
136 {
137 }
138
139 CachedResourceLoader::~CachedResourceLoader()
140 {
141     m_documentLoader = nullptr;
142     m_document = nullptr;
143
144     clearPreloads();
145     for (auto& resource : m_documentResources.values())
146         resource->setOwningCachedResourceLoader(nullptr);
147
148     // Make sure no requests still point to this CachedResourceLoader
149     ASSERT(m_requestCount == 0);
150 }
151
152 CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const
153 {
154     ASSERT(!resourceURL.isNull());
155     return cachedResource(MemoryCache::removeFragmentIdentifierIfNeeded(m_document->completeURL(resourceURL)));
156 }
157
158 CachedResource* CachedResourceLoader::cachedResource(const URL& url) const
159 {
160     ASSERT(!MemoryCache::shouldRemoveFragmentIdentifier(url));
161     return m_documentResources.get(url).get();
162 }
163
164 Frame* CachedResourceLoader::frame() const
165 {
166     return m_documentLoader ? m_documentLoader->frame() : nullptr;
167 }
168
169 SessionID CachedResourceLoader::sessionID() const
170 {
171     SessionID sessionID = SessionID::defaultSessionID();
172
173     if (Frame* f = frame())
174         sessionID = f->page()->sessionID();
175
176     return sessionID;
177 }
178
179 CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest&& request)
180 {
181     if (Frame* frame = this->frame()) {
182         if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) {
183             if (Document* document = frame->document())
184                 request.upgradeInsecureRequestIfNeeded(*document);
185             URL requestURL = request.resourceRequest().url();
186             if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request, ForPreload::No))
187                 PingLoader::loadImage(*frame, requestURL);
188             return nullptr;
189         }
190     }
191
192     auto defer = clientDefersImage(request.resourceRequest().url()) ? DeferOption::DeferredByClient : DeferOption::NoDefer;
193     return downcast<CachedImage>(requestResource(CachedResource::ImageResource, WTFMove(request), ForPreload::No, defer).get());
194 }
195
196 CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG)
197 {
198 #if ENABLE(SVG_FONTS)
199     if (isSVG)
200         return downcast<CachedSVGFont>(requestResource(CachedResource::SVGFontResource, WTFMove(request)).get());
201 #else
202     UNUSED_PARAM(isSVG);
203 #endif
204     return downcast<CachedFont>(requestResource(CachedResource::FontResource, WTFMove(request)).get());
205 }
206
207 #if ENABLE(VIDEO_TRACK)
208 CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest&& request)
209 {
210     return downcast<CachedTextTrack>(requestResource(CachedResource::TextTrackResource, WTFMove(request)).get());
211 }
212 #endif
213
214 CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest&& request)
215 {
216     return downcast<CachedCSSStyleSheet>(requestResource(CachedResource::CSSStyleSheet, WTFMove(request)).get());
217 }
218
219 CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest&& request)
220 {
221 #if ENABLE(CACHE_PARTITIONING)
222     ASSERT(document());
223     request.setDomainForCachePartition(*document());
224 #endif
225
226     auto& memoryCache = MemoryCache::singleton();
227     if (request.allowsCaching()) {
228         if (CachedResource* existing = memoryCache.resourceForRequest(request.resourceRequest(), sessionID())) {
229             if (is<CachedCSSStyleSheet>(*existing))
230                 return downcast<CachedCSSStyleSheet>(existing);
231             memoryCache.remove(*existing);
232         }
233     }
234
235     request.removeFragmentIdentifierIfNeeded();
236
237     CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(WTFMove(request), sessionID());
238
239     if (userSheet->allowsCaching())
240         memoryCache.add(*userSheet);
241     // FIXME: loadResource calls setOwningCachedResourceLoader() if the resource couldn't be added to cache. Does this function need to call it, too?
242
243     userSheet->load(*this);
244     return userSheet;
245 }
246
247 CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest&& request)
248 {
249     return downcast<CachedScript>(requestResource(CachedResource::Script, WTFMove(request)).get());
250 }
251
252 #if ENABLE(XSLT)
253 CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest&& request)
254 {
255     return downcast<CachedXSLStyleSheet>(requestResource(CachedResource::XSLStyleSheet, WTFMove(request)).get());
256 }
257 #endif
258
259 CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest&& request)
260 {
261     return downcast<CachedSVGDocument>(requestResource(CachedResource::SVGDocumentResource, WTFMove(request)).get());
262 }
263
264 #if ENABLE(LINK_PREFETCH)
265 CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest&& request)
266 {
267     ASSERT(frame());
268     ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource);
269     return requestResource(type, WTFMove(request));
270 }
271 #endif
272
273 CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMedia(CachedResourceRequest&& request)
274 {
275     return downcast<CachedRawResource>(requestResource(CachedResource::MediaResource, WTFMove(request)).get());
276 }
277
278 CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest&& request)
279 {
280     return downcast<CachedRawResource>(requestResource(CachedResource::RawResource, WTFMove(request)).get());
281 }
282
283 CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest&& request)
284 {
285     return downcast<CachedRawResource>(requestResource(CachedResource::MainResource, WTFMove(request)).get());
286 }
287
288 static MixedContentChecker::ContentType contentTypeFromResourceType(CachedResource::Type type)
289 {
290     switch (type) {
291     // https://w3c.github.io/webappsec-mixed-content/#category-optionally-blockable
292     // Editor's Draft, 11 February 2016
293     // 3.1. Optionally-blockable Content
294     case CachedResource::ImageResource:
295     case CachedResource::MediaResource:
296             return MixedContentChecker::ContentType::ActiveCanWarn;
297
298     case CachedResource::CSSStyleSheet:
299     case CachedResource::Script:
300     case CachedResource::FontResource:
301         return MixedContentChecker::ContentType::Active;
302
303 #if ENABLE(SVG_FONTS)
304     case CachedResource::SVGFontResource:
305         return MixedContentChecker::ContentType::Active;
306 #endif
307
308     case CachedResource::RawResource:
309     case CachedResource::SVGDocumentResource:
310         return MixedContentChecker::ContentType::Active;
311 #if ENABLE(XSLT)
312     case CachedResource::XSLStyleSheet:
313         return MixedContentChecker::ContentType::Active;
314 #endif
315
316 #if ENABLE(LINK_PREFETCH)
317     case CachedResource::LinkPrefetch:
318     case CachedResource::LinkSubresource:
319         return MixedContentChecker::ContentType::Active;
320 #endif
321
322 #if ENABLE(VIDEO_TRACK)
323     case CachedResource::TextTrackResource:
324         return MixedContentChecker::ContentType::Active;
325 #endif
326     default:
327         ASSERT_NOT_REACHED();
328         return MixedContentChecker::ContentType::Active;
329     }
330 }
331
332 bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const URL& url) const
333 {
334
335     if (!canRequestInContentDispositionAttachmentSandbox(type, url))
336         return false;
337
338     switch (type) {
339     case CachedResource::Script:
340 #if ENABLE(XSLT)
341     case CachedResource::XSLStyleSheet:
342 #endif
343     case CachedResource::SVGDocumentResource:
344     case CachedResource::CSSStyleSheet:
345         // These resource can inject script into the current document (Script,
346         // XSL) or exfiltrate the content of the current document (CSS).
347         if (Frame* f = frame()) {
348             if (!f->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url))
349                 return false;
350             Frame& top = f->tree().top();
351             if (&top != f && !top.loader().mixedContentChecker().canRunInsecureContent(top.document()->securityOrigin(), url))
352                 return false;
353         }
354         break;
355 #if ENABLE(VIDEO_TRACK)
356     case CachedResource::TextTrackResource:
357 #endif
358     case CachedResource::MediaResource:
359     case CachedResource::RawResource:
360     case CachedResource::ImageResource:
361 #if ENABLE(SVG_FONTS)
362     case CachedResource::SVGFontResource:
363 #endif
364     case CachedResource::FontResource: {
365         // These resources can corrupt only the frame's pixels.
366         if (Frame* f = frame()) {
367             Frame& topFrame = f->tree().top();
368             if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), contentTypeFromResourceType(type), url))
369                 return false;
370         }
371         break;
372     }
373     case CachedResource::MainResource:
374 #if ENABLE(LINK_PREFETCH)
375     case CachedResource::LinkPrefetch:
376     case CachedResource::LinkSubresource:
377         // Prefetch cannot affect the current document.
378 #endif
379         break;
380     }
381     return true;
382 }
383
384 bool CachedResourceLoader::allowedByContentSecurityPolicy(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, ContentSecurityPolicy::RedirectResponseReceived redirectResponseReceived) const
385 {
386     if (options.contentSecurityPolicyImposition == ContentSecurityPolicyImposition::SkipPolicyCheck)
387         return true;
388
389     ASSERT(m_document);
390     ASSERT(m_document->contentSecurityPolicy());
391
392     switch (type) {
393 #if ENABLE(XSLT)
394     case CachedResource::XSLStyleSheet:
395 #endif
396     case CachedResource::Script:
397         if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url, redirectResponseReceived))
398             return false;
399         break;
400     case CachedResource::CSSStyleSheet:
401         if (!m_document->contentSecurityPolicy()->allowStyleFromSource(url, redirectResponseReceived))
402             return false;
403         break;
404     case CachedResource::SVGDocumentResource:
405     case CachedResource::ImageResource:
406         if (!m_document->contentSecurityPolicy()->allowImageFromSource(url, redirectResponseReceived))
407             return false;
408         break;
409 #if ENABLE(SVG_FONTS)
410     case CachedResource::SVGFontResource:
411 #endif
412     case CachedResource::FontResource:
413         if (!m_document->contentSecurityPolicy()->allowFontFromSource(url, redirectResponseReceived))
414             return false;
415         break;
416     case CachedResource::MediaResource:
417 #if ENABLE(VIDEO_TRACK)
418     case CachedResource::TextTrackResource:
419 #endif
420         if (!m_document->contentSecurityPolicy()->allowMediaFromSource(url, redirectResponseReceived))
421             return false;
422         break;
423     case CachedResource::RawResource:
424         return true;
425     default:
426         ASSERT_NOT_REACHED();
427     }
428
429     return true;
430 }
431
432 static inline bool isSameOriginDataURL(const URL& url, const ResourceLoaderOptions& options)
433 {
434     // FIXME: Remove same-origin data URL flag since it was removed from fetch spec (https://github.com/whatwg/fetch/issues/381).
435     return url.protocolIsData() && options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set;
436 }
437
438 bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const CachedResourceRequest& request, ForPreload forPreload)
439 {
440     auto& options = request.options();
441
442     if (document() && !document()->securityOrigin()->canDisplay(url)) {
443         if (forPreload == ForPreload::No)
444             FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength());
445         LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
446         return false;
447     }
448
449     if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin()->canRequest(url) && !isSameOriginDataURL(url, options)) {
450         printAccessDeniedMessage(url);
451         return false;
452     }
453
454     if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::No))
455         return false;
456
457     // SVG Images have unique security rules that prevent all subresource requests except for data urls.
458     if (type != CachedResource::MainResource && frame() && frame()->page()) {
459         if (frame()->page()->chrome().client().isSVGImageChromeClient() && !url.protocolIsData())
460             return false;
461     }
462
463     // Last of all, check for insecure content. We do this last so that when folks block insecure content with a CSP policy, they don't get a warning.
464     // They'll still get a warning in the console about CSP blocking the load.
465
466     // FIXME: Should we consider whether the request is for preload here?
467     if (!checkInsecureContent(type, url))
468         return false;
469
470     return true;
471 }
472
473 // FIXME: Should we find a way to know whether the redirection is for a preload request like we do for CachedResourceLoader::canRequest?
474 bool CachedResourceLoader::canRequestAfterRedirection(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options) const
475 {
476     if (document() && !document()->securityOrigin()->canDisplay(url)) {
477         FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength());
478         LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
479         return false;
480     }
481
482     // FIXME: According to https://fetch.spec.whatwg.org/#http-redirect-fetch, we should check that the URL is HTTP(s) except if in navigation mode.
483     // But we currently allow at least data URLs to be loaded.
484
485     if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin()->canRequest(url)) {
486         printAccessDeniedMessage(url);
487         return false;
488     }
489
490     if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::Yes))
491         return false;
492
493     // Last of all, check for insecure content. We do this last so that when folks block insecure content with a CSP policy, they don't get a warning.
494     // They'll still get a warning in the console about CSP blocking the load.
495     if (!checkInsecureContent(type, url))
496         return false;
497
498     return true;
499 }
500
501 bool CachedResourceLoader::updateRequestAfterRedirection(CachedResource::Type type, ResourceRequest& request, const ResourceLoaderOptions& options)
502 {
503     ASSERT(m_documentLoader);
504     if (auto* document = m_documentLoader->cachedResourceLoader().document())
505         upgradeInsecureResourceRequestIfNeeded(request, *document);
506
507     // FIXME: We might want to align the checks done here with the ones done in CachedResourceLoader::requestResource, content extensions blocking in particular.
508
509     return canRequestAfterRedirection(type, request.url(), options);
510 }
511
512 bool CachedResourceLoader::canRequestInContentDispositionAttachmentSandbox(CachedResource::Type type, const URL& url) const
513 {
514     Document* document;
515
516     // FIXME: Do we want to expand this to all resource types that the mixed content checker would consider active content?
517     switch (type) {
518     case CachedResource::MainResource:
519         if (auto ownerElement = frame() ? frame()->ownerElement() : nullptr) {
520             document = &ownerElement->document();
521             break;
522         }
523         return true;
524     case CachedResource::CSSStyleSheet:
525         document = m_document;
526         break;
527     default:
528         return true;
529     }
530
531     if (!document->shouldEnforceContentDispositionAttachmentSandbox() || document->securityOrigin()->canRequest(url))
532         return true;
533
534     String message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from document with Content-Disposition: attachment at URL " + document->url().stringCenterEllipsizedToLength() + ".";
535     document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
536     return false;
537 }
538
539 bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource* resource)
540 {
541     if (!resource || !frame() || resource->status() != CachedResource::Cached)
542         return true;
543
544     ResourceRequest newRequest = ResourceRequest(resource->url());
545     if (request.resourceRequest().hiddenFromInspector())
546         newRequest.setHiddenFromInspector(true);
547     frame()->loader().loadedResourceFromMemoryCache(resource, newRequest);
548
549     // FIXME <http://webkit.org/b/113251>: If the delegate modifies the request's
550     // URL, it is no longer appropriate to use this CachedResource.
551     return !newRequest.isNull();
552 }
553
554 bool CachedResourceLoader::shouldUpdateCachedResourceWithCurrentRequest(const CachedResource& resource, const CachedResourceRequest& request)
555 {
556     // WebKit is not supporting CORS for fonts (https://bugs.webkit.org/show_bug.cgi?id=86817), no need to update the resource before reusing it.
557     if (resource.type() == CachedResource::Type::FontResource)
558         return false;
559
560 #if ENABLE(SVG_FONTS)
561     if (resource.type() == CachedResource::Type::SVGFontResource)
562         return false;
563 #endif
564
565 #if ENABLE(XSLT)
566     // Load is same-origin, we do not check for CORS.
567     if (resource.type() == CachedResource::XSLStyleSheet)
568         return false;
569 #endif
570
571     // FIXME: We should enable resource reuse for these resource types
572     switch (resource.type()) {
573     case CachedResource::SVGDocumentResource:
574         return false;
575     case CachedResource::MainResource:
576         return false;
577 #if ENABLE(LINK_PREFETCH)
578     case CachedResource::LinkPrefetch:
579         return false;
580     case CachedResource::LinkSubresource:
581         return false;
582 #endif
583     default:
584         break;
585     }
586
587     if (resource.options().mode != request.options().mode || !originsMatch(request.origin(), resource.origin()))
588         return true;
589
590     if (resource.options().redirect != request.options().redirect && resource.hasRedirections())
591         return true;
592
593     return false;
594 }
595
596 static inline bool isResourceSuitableForDirectReuse(const CachedResource& resource, const CachedResourceRequest& request)
597 {
598     // FIXME: For being loaded requests, the response tainting may not be correctly computed if the fetch mode is not the same.
599     // Even if the fetch mode is the same, we are not sure that the resource can be reused (Vary: Origin header for instance).
600     // We should find a way to improve this.
601     if (resource.status() != CachedResource::Cached)
602         return false;
603
604     // If the cached resource has not followed redirections, it is incomplete and we should not use it.
605     // Let's make sure the memory cache has no such resource.
606     ASSERT(resource.response().type() != ResourceResponse::Type::Opaqueredirect);
607
608     // We could support redirect modes other than Follow in case of a redirected resource.
609     // This case is rare and is not worth optimizing currently.
610     if (request.options().redirect != FetchOptions::Redirect::Follow && resource.hasRedirections())
611         return false;
612
613     // FIXME: Implement reuse of cached raw resources.
614     if (resource.type() == CachedResource::Type::RawResource || resource.type() == CachedResource::Type::MediaResource)
615         return false;
616
617     return true;
618 }
619
620 CachedResourceHandle<CachedResource> CachedResourceLoader::updateCachedResourceWithCurrentRequest(const CachedResource& resource, CachedResourceRequest&& request)
621 {
622     if (!isResourceSuitableForDirectReuse(resource, request)) {
623         request.setCachingPolicy(CachingPolicy::DisallowCaching);
624         return loadResource(resource.type(), WTFMove(request));
625     }
626
627     auto resourceHandle = createResource(resource.type(), WTFMove(request), sessionID());
628     resourceHandle->loadFrom(resource);
629     return resourceHandle;
630 }
631
632 static inline void logMemoryCacheResourceRequest(Frame* frame, const String& description, const String& value = String())
633 {
634     if (!frame || !frame->page())
635         return;
636     if (value.isNull())
637         frame->page()->diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::resourceRequestKey(), description, ShouldSample::Yes);
638     else
639         frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceRequestKey(), description, value, ShouldSample::Yes);
640 }
641
642 void CachedResourceLoader::prepareFetch(CachedResource::Type type, CachedResourceRequest& request)
643 {
644     // Implementing step 1 to 7 of https://fetch.spec.whatwg.org/#fetching
645
646     if (!request.origin() && document())
647         request.setOrigin(document()->securityOrigin());
648
649     request.setAcceptHeaderIfNone(type);
650
651     // Accept-Language value is handled in underlying port-specific code.
652     // FIXME: Decide whether to support client hints
653 }
654
655 void CachedResourceLoader::updateHTTPRequestHeaders(CachedResource::Type type, CachedResourceRequest& request)
656 {
657     // Implementing steps 7 to 12 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
658
659     // FIXME: We should reconcile handling of MainResource with other resources.
660     if (type != CachedResource::Type::MainResource) {
661         // In some cases we may try to load resources in frameless documents. Such loads always fail.
662         // FIXME: We shouldn't need to do the check on frame.
663         if (auto* frame = this->frame())
664             request.updateReferrerOriginAndUserAgentHeaders(frame->loader(), document() ? document()->referrerPolicy() : ReferrerPolicy::Default);
665     }
666
667     request.updateAccordingCacheMode();
668 }
669
670 CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, DeferOption defer)
671 {
672     if (Document* document = this->document())
673         request.upgradeInsecureRequestIfNeeded(*document);
674
675     URL url = request.resourceRequest().url();
676
677     LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority() ? static_cast<int>(request.priority().value()) : -1, forPreload == ForPreload::Yes);
678
679     if (!url.isValid()) {
680         RELEASE_LOG_IF_ALLOWED("requestResource: URL is invalid (frame = %p)", frame());
681         return nullptr;
682     }
683
684     prepareFetch(type, request);
685
686     // We are passing url as well as request, as request url may contain a fragment identifier.
687     if (!canRequest(type, url, request, forPreload)) {
688         RELEASE_LOG_IF_ALLOWED("requestResource: Not allowed to request resource (frame = %p)", frame());
689         return nullptr;
690     }
691
692 #if ENABLE(CONTENT_EXTENSIONS)
693     if (frame() && frame()->mainFrame().page() && m_documentLoader) {
694         const auto& resourceRequest = request.resourceRequest();
695         auto blockedStatus = frame()->mainFrame().page()->userContentProvider().processContentExtensionRulesForLoad(resourceRequest.url(), toResourceType(type), *m_documentLoader);
696         request.applyBlockedStatus(blockedStatus);
697         if (blockedStatus.blockedLoad) {
698             RELEASE_LOG_IF_ALLOWED("requestResource: Resource blocked by content blocker (frame = %p)", frame());
699             if (type == CachedResource::Type::MainResource) {
700                 auto resource = createResource(type, WTFMove(request), sessionID());
701                 ASSERT(resource);
702                 resource->error(CachedResource::Status::LoadError);
703                 resource->setResourceError(ResourceError(ContentExtensions::WebKitContentBlockerDomain, 0, resourceRequest.url(), WEB_UI_STRING("The URL was blocked by a content blocker", "WebKitErrorBlockedByContentBlocker description")));
704                 return resource;
705             }
706             return nullptr;
707         }
708         if (blockedStatus.madeHTTPS
709             && type == CachedResource::Type::MainResource
710             && m_documentLoader->isLoadingMainResource()) {
711             // This is to make sure the correct 'new' URL shows in the location bar.
712             m_documentLoader->frameLoader()->client().dispatchDidChangeProvisionalURL();
713         }
714         url = request.resourceRequest().url(); // The content extension could have changed it from http to https.
715         url = MemoryCache::removeFragmentIdentifierIfNeeded(url); // Might need to remove fragment identifier again.
716     }
717 #endif
718
719 #if ENABLE(WEB_TIMING)
720     LoadTiming loadTiming;
721     loadTiming.markStartTimeAndFetchStart();
722 #endif
723
724     if (request.resourceRequest().url().protocolIsInHTTPFamily())
725         updateHTTPRequestHeaders(type, request);
726
727     auto& memoryCache = MemoryCache::singleton();
728     if (request.allowsCaching() && memoryCache.disabled()) {
729         DocumentResourceMap::iterator it = m_documentResources.find(url.string());
730         if (it != m_documentResources.end()) {
731             it->value->setOwningCachedResourceLoader(nullptr);
732             m_documentResources.remove(it);
733         }
734     }
735
736     // See if we can use an existing resource from the cache.
737     CachedResourceHandle<CachedResource> resource;
738 #if ENABLE(CACHE_PARTITIONING)
739     if (document())
740         request.setDomainForCachePartition(*document());
741 #endif
742
743     if (request.allowsCaching())
744         resource = memoryCache.resourceForRequest(request.resourceRequest(), sessionID());
745
746     logMemoryCacheResourceRequest(frame(), resource ? DiagnosticLoggingKeys::inMemoryCacheKey() : DiagnosticLoggingKeys::notInMemoryCacheKey());
747
748     RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, defer);
749     switch (policy) {
750     case Reload:
751         memoryCache.remove(*resource);
752         FALLTHROUGH;
753     case Load:
754         if (resource)
755             logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedKey());
756         resource = loadResource(type, WTFMove(request));
757         break;
758     case Revalidate:
759         if (resource)
760             logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::revalidatingKey());
761         resource = revalidateResource(WTFMove(request), *resource);
762         break;
763     case Use:
764         ASSERT(resource);
765         if (shouldUpdateCachedResourceWithCurrentRequest(*resource, request)) {
766             resource = updateCachedResourceWithCurrentRequest(*resource, WTFMove(request));
767             if (resource->status() != CachedResource::Status::Cached)
768                 policy = Load;
769         } else {
770             if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, resource.get()))
771                 return nullptr;
772             logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::usedKey());
773             memoryCache.resourceAccessed(*resource);
774 #if ENABLE(WEB_TIMING)
775             if (document() && RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) {
776                 // FIXME (161170): The networkLoadTiming shouldn't be stored on the ResourceResponse.
777                 resource->response().networkLoadTiming().reset();
778                 loadTiming.setResponseEnd(monotonicallyIncreasingTime());
779                 m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, request.initiatorName(), frame());
780                 m_resourceTimingInfo.addResourceTiming(resource.get(), *document(), loadTiming);
781             }
782 #endif
783             if (forPreload == ForPreload::No)
784                 resource->setLoadPriority(request.priority());
785         }
786         break;
787     }
788
789     if (!resource)
790         return nullptr;
791
792     if (forPreload == ForPreload::No && resource->loader() && resource->resourceRequest().ignoreForRequestCount()) {
793         resource->resourceRequest().setIgnoreForRequestCount(false);
794         incrementRequestCount(*resource);
795     }
796
797     if ((policy != Use || resource->stillNeedsLoad()) && defer == DeferOption::NoDefer) {
798         resource->load(*this);
799
800         // We don't support immediate loads, but we do support immediate failure.
801         if (resource->errorOccurred()) {
802             if (resource->allowsCaching() && resource->inCache())
803                 memoryCache.remove(*resource);
804             return nullptr;
805         }
806     }
807
808     if (document() && !document()->loadEventFinished() && !resource->resourceRequest().url().protocolIsData())
809         m_validatedURLs.add(resource->resourceRequest().url());
810
811     ASSERT(resource->url() == url.string());
812     m_documentResources.set(resource->url(), resource);
813     return resource;
814 }
815
816 void CachedResourceLoader::documentDidFinishLoadEvent()
817 {
818     m_validatedURLs.clear();
819 }
820
821 CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(CachedResourceRequest&& request, CachedResource& resource)
822 {
823     ASSERT(resource.inCache());
824     auto& memoryCache = MemoryCache::singleton();
825     ASSERT(!memoryCache.disabled());
826     ASSERT(resource.canUseCacheValidator());
827     ASSERT(!resource.resourceToRevalidate());
828     ASSERT(resource.sessionID() == sessionID());
829     ASSERT(resource.allowsCaching());
830
831 #if ENABLE(WEB_TIMING)
832     AtomicString initiatorName = request.initiatorName();
833 #endif
834     CachedResourceHandle<CachedResource> newResource = createResource(resource.type(), WTFMove(request), resource.sessionID());
835
836     LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), &resource);
837     newResource->setResourceToRevalidate(&resource);
838
839     memoryCache.remove(resource);
840     memoryCache.add(*newResource);
841 #if ENABLE(WEB_TIMING)
842     if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
843         m_resourceTimingInfo.storeResourceTimingInitiatorInformation(newResource, initiatorName, frame());
844 #endif
845     return newResource;
846 }
847
848 CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest&& request)
849 {
850     auto& memoryCache = MemoryCache::singleton();
851     ASSERT(!request.allowsCaching() || !memoryCache.resourceForRequest(request.resourceRequest(), sessionID())
852         || request.resourceRequest().cachePolicy() == DoNotUseAnyCache || request.resourceRequest().cachePolicy() == ReloadIgnoringCacheData || request.resourceRequest().cachePolicy() == RefreshAnyCacheData);
853
854     LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data());
855
856 #if ENABLE(WEB_TIMING)
857     AtomicString initiatorName = request.initiatorName();
858 #endif
859     CachedResourceHandle<CachedResource> resource = createResource(type, WTFMove(request), sessionID());
860
861     if (resource->allowsCaching() && !memoryCache.add(*resource))
862         resource->setOwningCachedResourceLoader(this);
863 #if ENABLE(WEB_TIMING)
864     if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
865         m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, initiatorName, frame());
866 #endif
867     return resource;
868 }
869
870 static void logRevalidation(const String& reason, DiagnosticLoggingClient& logClient)
871 {
872     logClient.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), DiagnosticLoggingKeys::reasonKey(), reason, ShouldSample::Yes);
873 }
874
875 static void logResourceRevalidationDecision(CachedResource::RevalidationDecision reason, const Frame* frame)
876 {
877     if (!frame || !frame->page())
878         return;
879     auto& logClient = frame->page()->diagnosticLoggingClient();
880     switch (reason) {
881     case CachedResource::RevalidationDecision::No:
882         break;
883     case CachedResource::RevalidationDecision::YesDueToExpired:
884         logRevalidation(DiagnosticLoggingKeys::isExpiredKey(), logClient);
885         break;
886     case CachedResource::RevalidationDecision::YesDueToNoStore:
887         logRevalidation(DiagnosticLoggingKeys::noStoreKey(), logClient);
888         break;
889     case CachedResource::RevalidationDecision::YesDueToNoCache:
890         logRevalidation(DiagnosticLoggingKeys::noCacheKey(), logClient);
891         break;
892     case CachedResource::RevalidationDecision::YesDueToCachePolicy:
893         logRevalidation(DiagnosticLoggingKeys::reloadKey(), logClient);
894         break;
895     }
896 }
897
898 CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, CachedResourceRequest& cachedResourceRequest, CachedResource* existingResource, ForPreload forPreload, DeferOption defer) const
899 {
900     auto& request = cachedResourceRequest.resourceRequest();
901
902     if (!existingResource)
903         return Load;
904
905     if (request.cachePolicy() == DoNotUseAnyCache || request.cachePolicy() == ReloadIgnoringCacheData)
906         return Load;
907
908     if (request.cachePolicy() == RefreshAnyCacheData)
909         return Reload;
910
911     // We already have a preload going for this URL.
912     if (forPreload == ForPreload::Yes && existingResource->isPreloaded())
913         return Use;
914
915     // If the same URL has been loaded as a different type, we need to reload.
916     if (existingResource->type() != type) {
917         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch.");
918         logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonTypeMismatchKey());
919         return Reload;
920     }
921
922     if (!existingResource->varyHeaderValuesMatch(request))
923         return Reload;
924
925     auto* textDecoder = existingResource->textResourceDecoder();
926     if (textDecoder && !textDecoder->hasEqualEncodingForCharset(cachedResourceRequest.charset()))
927         return Reload;
928
929     // FIXME: We should use the same cache policy for all resource types. The raw resource policy is overly strict
930     //        while the normal subresource policy is too loose.
931     if (existingResource->isMainOrMediaOrRawResource() && frame()) {
932         bool strictPolicyDisabled = frame()->loader().isStrictRawResourceValidationPolicyDisabledForTesting();
933         bool canReuseRawResource = strictPolicyDisabled || downcast<CachedRawResource>(*existingResource).canReuse(request);
934         if (!canReuseRawResource)
935             return Reload;
936     }
937
938     // Conditional requests should have failed canReuse check.
939     ASSERT(!request.isConditional());
940
941     // Do not load from cache if images are not enabled. The load for this image will be blocked in CachedImage::load.
942     if (defer == DeferOption::DeferredByClient)
943         return Reload;
944
945     // Don't reload resources while pasting or if cache mode allows stale resources.
946     if (m_allowStaleResources || cachedResourceRequest.options().cache == FetchOptions::Cache::ForceCache || cachedResourceRequest.options().cache == FetchOptions::Cache::OnlyIfCached)
947         return Use;
948
949     ASSERT(cachedResourceRequest.options().cache == FetchOptions::Cache::Default || cachedResourceRequest.options().cache == FetchOptions::Cache::NoCache);
950
951     // Always use preloads.
952     if (existingResource->isPreloaded())
953         return Use;
954
955     // We can find resources that are being validated from cache only when validation is just successfully completing.
956     if (existingResource->validationCompleting())
957         return Use;
958     ASSERT(!existingResource->validationInProgress());
959
960     // Validate the redirect chain.
961     bool cachePolicyIsHistoryBuffer = cachePolicy(type) == CachePolicyHistoryBuffer;
962     if (!existingResource->redirectChainAllowsReuse(cachePolicyIsHistoryBuffer ? ReuseExpiredRedirection : DoNotReuseExpiredRedirection)) {
963         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to not cached or expired redirections.");
964         logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonRedirectChainKey());
965         return Reload;
966     }
967
968     // CachePolicyHistoryBuffer uses the cache except if this is a main resource with "cache-control: no-store".
969     if (cachePolicyIsHistoryBuffer) {
970         // FIXME: Ignoring "cache-control: no-cache" for sub-resources on history navigation but not the main
971         // resource is inconsistent. We should probably harmonize this.
972         if (!existingResource->response().cacheControlContainsNoStore() || type != CachedResource::MainResource)
973             return Use;
974     }
975
976     // Don't reuse resources with Cache-control: no-store.
977     if (existingResource->response().cacheControlContainsNoStore()) {
978         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store.");
979         logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonNoStoreKey());
980         return Reload;
981     }
982
983     // If credentials were sent with the previous request and won't be
984     // with this one, or vice versa, re-fetch the resource.
985     //
986     // This helps with the case where the server sends back
987     // "Access-Control-Allow-Origin: *" all the time, but some of the
988     // client's requests are made without CORS and some with.
989     if (existingResource->resourceRequest().allowCookies() != request.allowCookies()) {
990         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to difference in credentials settings.");
991         logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonCredentialSettingsKey());
992         return Reload;
993     }
994
995     // During the initial load, avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to.
996     if (document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url()))
997         return Use;
998
999     // CachePolicyReload always reloads
1000     if (cachePolicy(type) == CachePolicyReload) {
1001         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload.");
1002         logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonReloadKey());
1003         return Reload;
1004     }
1005     
1006     // We'll try to reload the resource if it failed last time.
1007     if (existingResource->errorOccurred()) {
1008         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state");
1009         logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonErrorKey());
1010         return Reload;
1011     }
1012
1013     if (existingResource->isLoading()) {
1014         // Do not use cached main resources that are still loading because sharing
1015         // loading CachedResources in this case causes issues with regards to cancellation.
1016         // If one of the DocumentLoader clients decides to cancel the load, then the load
1017         // would be cancelled for all other DocumentLoaders as well.
1018         if (type == CachedResource::Type::MainResource)
1019             return Reload;
1020         // For cached subresources that are still loading we ignore the cache policy.
1021         return Use;
1022     }
1023
1024     auto revalidationDecision = existingResource->makeRevalidationDecision(cachePolicy(type));
1025     logResourceRevalidationDecision(revalidationDecision, frame());
1026
1027     // Check if the cache headers requires us to revalidate (cache expiration for example).
1028     if (revalidationDecision != CachedResource::RevalidationDecision::No) {
1029         // See if the resource has usable ETag or Last-modified headers.
1030         if (existingResource->canUseCacheValidator())
1031             return Revalidate;
1032         
1033         // No, must reload.
1034         LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators.");
1035         logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonMustRevalidateNoValidatorKey());
1036         return Reload;
1037     }
1038
1039     return Use;
1040 }
1041
1042 void CachedResourceLoader::printAccessDeniedMessage(const URL& url) const
1043 {
1044     if (url.isNull())
1045         return;
1046
1047     if (!frame())
1048         return;
1049
1050     String message;
1051     if (!m_document || m_document->url().isNull())
1052         message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + '.';
1053     else
1054         message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from frame with URL " + m_document->url().stringCenterEllipsizedToLength() + ". Domains, protocols and ports must match.\n";
1055
1056     frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
1057 }
1058
1059 void CachedResourceLoader::setAutoLoadImages(bool enable)
1060 {
1061     if (enable == m_autoLoadImages)
1062         return;
1063
1064     m_autoLoadImages = enable;
1065
1066     if (!m_autoLoadImages)
1067         return;
1068
1069     reloadImagesIfNotDeferred();
1070 }
1071
1072 void CachedResourceLoader::setImagesEnabled(bool enable)
1073 {
1074     if (enable == m_imagesEnabled)
1075         return;
1076
1077     m_imagesEnabled = enable;
1078
1079     if (!m_imagesEnabled)
1080         return;
1081
1082     reloadImagesIfNotDeferred();
1083 }
1084
1085 bool CachedResourceLoader::clientDefersImage(const URL&) const
1086 {
1087     return !m_imagesEnabled;
1088 }
1089
1090 bool CachedResourceLoader::shouldPerformImageLoad(const URL& url) const
1091 {
1092     return m_autoLoadImages || url.protocolIsData();
1093 }
1094
1095 bool CachedResourceLoader::shouldDeferImageLoad(const URL& url) const
1096 {
1097     return clientDefersImage(url) || !shouldPerformImageLoad(url);
1098 }
1099
1100 void CachedResourceLoader::reloadImagesIfNotDeferred()
1101 {
1102     for (auto& resource : m_documentResources.values()) {
1103         if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && !clientDefersImage(resource->url()))
1104             downcast<CachedImage>(*resource).load(*this);
1105     }
1106 }
1107
1108 CachePolicy CachedResourceLoader::cachePolicy(CachedResource::Type type) const
1109 {
1110     Frame* frame = this->frame();
1111     if (!frame)
1112         return CachePolicyVerify;
1113
1114     if (type != CachedResource::MainResource)
1115         return frame->loader().subresourceCachePolicy();
1116
1117     if (Page* page = frame->page()) {
1118         if (page->isResourceCachingDisabled())
1119             return CachePolicyReload;
1120     }
1121
1122     switch (frame->loader().loadType()) {
1123     case FrameLoadType::ReloadFromOrigin:
1124     case FrameLoadType::Reload:
1125         return CachePolicyReload;
1126     case FrameLoadType::Back:
1127     case FrameLoadType::Forward:
1128     case FrameLoadType::IndexedBackForward:
1129         // Do not revalidate cached main resource on back/forward navigation.
1130         return CachePolicyHistoryBuffer;
1131     default:
1132         return CachePolicyVerify;
1133     }
1134 }
1135
1136 void CachedResourceLoader::removeCachedResource(CachedResource& resource)
1137 {
1138 #ifndef NDEBUG
1139     DocumentResourceMap::iterator it = m_documentResources.find(resource.url());
1140     if (it != m_documentResources.end())
1141         ASSERT(it->value.get() == &resource);
1142 #endif
1143     m_documentResources.remove(resource.url());
1144 }
1145
1146 void CachedResourceLoader::loadDone(CachedResource* resource, bool shouldPerformPostLoadActions)
1147 {
1148     RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader);
1149     RefPtr<Document> protectDocument(m_document);
1150
1151 #if ENABLE(WEB_TIMING)
1152     if (resource && document() && RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
1153         m_resourceTimingInfo.addResourceTiming(resource, *document(), resource->loader()->loadTiming());
1154 #else
1155     UNUSED_PARAM(resource);
1156 #endif
1157
1158     if (frame())
1159         frame()->loader().loadDone();
1160
1161     if (shouldPerformPostLoadActions)
1162         performPostLoadActions();
1163
1164     if (!m_garbageCollectDocumentResourcesTimer.isActive())
1165         m_garbageCollectDocumentResourcesTimer.startOneShot(0);
1166 }
1167
1168 // Garbage collecting m_documentResources is a workaround for the
1169 // CachedResourceHandles on the RHS being strong references. Ideally this
1170 // would be a weak map, however CachedResourceHandles perform additional
1171 // bookkeeping on CachedResources, so instead pseudo-GC them -- when the
1172 // reference count reaches 1, m_documentResources is the only reference, so
1173 // remove it from the map.
1174 void CachedResourceLoader::garbageCollectDocumentResources()
1175 {
1176     typedef Vector<String, 10> StringVector;
1177     StringVector resourcesToDelete;
1178
1179     for (auto& resource : m_documentResources) {
1180         if (resource.value->hasOneHandle()) {
1181             resourcesToDelete.append(resource.key);
1182             resource.value->setOwningCachedResourceLoader(nullptr);
1183         }
1184     }
1185
1186     for (auto& resource : resourcesToDelete)
1187         m_documentResources.remove(resource);
1188 }
1189
1190 void CachedResourceLoader::performPostLoadActions()
1191 {
1192     checkForPendingPreloads();
1193
1194     platformStrategies()->loaderStrategy()->servePendingRequests();
1195 }
1196
1197 void CachedResourceLoader::incrementRequestCount(const CachedResource& resource)
1198 {
1199     if (resource.ignoreForRequestCount())
1200         return;
1201
1202     ++m_requestCount;
1203 }
1204
1205 void CachedResourceLoader::decrementRequestCount(const CachedResource& resource)
1206 {
1207     if (resource.ignoreForRequestCount())
1208         return;
1209
1210     --m_requestCount;
1211     ASSERT(m_requestCount > -1);
1212 }
1213
1214 CachedResourceHandle<CachedResource> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request, PreloadType preloadType)
1215 {
1216     // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>.
1217     // FIXME: We should consider adding a setting to toggle aggressive preloading behavior as opposed
1218     // to making this behavior specific to iOS.
1219 #if !PLATFORM(IOS)
1220     bool hasRendering = m_document->bodyOrFrameset() && m_document->renderView();
1221     bool canBlockParser = type == CachedResource::Script || type == CachedResource::CSSStyleSheet;
1222     if (!hasRendering && !canBlockParser && preloadType == ImplicitPreload) {
1223         // Don't preload subresources that can't block the parser before we have something to draw.
1224         // This helps prevent preloads from delaying first display when bandwidth is limited.
1225         PendingPreload pendingPreload = { type, WTFMove(request) };
1226         m_pendingPreloads.append(pendingPreload);
1227         return nullptr;
1228     }
1229 #else
1230     UNUSED_PARAM(preloadType);
1231 #endif
1232     return requestPreload(type, WTFMove(request));
1233 }
1234
1235 void CachedResourceLoader::checkForPendingPreloads()
1236 {
1237     if (m_pendingPreloads.isEmpty())
1238         return;
1239     auto* body = m_document->bodyOrFrameset();
1240     if (!body || !body->renderer())
1241         return;
1242 #if PLATFORM(IOS)
1243     // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>.
1244     // So, we should never have any pending preloads.
1245     // FIXME: We should look to avoid compiling this code entirely when building for iOS.
1246     ASSERT_NOT_REACHED();
1247 #endif
1248     while (!m_pendingPreloads.isEmpty()) {
1249         PendingPreload preload = m_pendingPreloads.takeFirst();
1250         // 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).
1251         if (!cachedResource(preload.m_request.resourceRequest().url()))
1252             requestPreload(preload.m_type, WTFMove(preload.m_request));
1253     }
1254     m_pendingPreloads.clear();
1255 }
1256
1257 CachedResourceHandle<CachedResource> CachedResourceLoader::requestPreload(CachedResource::Type type, CachedResourceRequest&& request)
1258 {
1259     if (request.charset().isEmpty() && (type == CachedResource::Script || type == CachedResource::CSSStyleSheet))
1260         request.setCharset(m_document->charset());
1261
1262     CachedResourceHandle<CachedResource> resource = requestResource(type, WTFMove(request), ForPreload::Yes);
1263     if (!resource || (m_preloads && m_preloads->contains(resource.get())))
1264         return nullptr;
1265     // Fonts need special treatment since just creating the resource doesn't trigger a load.
1266     if (type == CachedResource::FontResource)
1267         downcast<CachedFont>(resource.get())->beginLoadIfNeeded(*this);
1268     resource->increasePreloadCount();
1269
1270     if (!m_preloads)
1271         m_preloads = std::make_unique<ListHashSet<CachedResource*>>();
1272     m_preloads->add(resource.get());
1273
1274 #if PRELOAD_DEBUG
1275     printf("PRELOADING %s\n",  resource->url().latin1().data());
1276 #endif
1277     return resource;
1278 }
1279
1280 bool CachedResourceLoader::isPreloaded(const String& urlString) const
1281 {
1282     const URL& url = m_document->completeURL(urlString);
1283
1284     if (m_preloads) {
1285         for (auto& resource : *m_preloads) {
1286             if (resource->url() == url)
1287                 return true;
1288         }
1289     }
1290
1291     for (auto& pendingPreload : m_pendingPreloads) {
1292         if (pendingPreload.m_request.resourceRequest().url() == url)
1293             return true;
1294     }
1295     return false;
1296 }
1297
1298 void CachedResourceLoader::clearPreloads()
1299 {
1300 #if PRELOAD_DEBUG
1301     printPreloadStats();
1302 #endif
1303     if (!m_preloads)
1304         return;
1305
1306     for (auto* resource : *m_preloads) {
1307         resource->decreasePreloadCount();
1308         bool deleted = resource->deleteIfPossible();
1309         if (!deleted && resource->preloadResult() == CachedResource::PreloadNotReferenced)
1310             MemoryCache::singleton().remove(*resource);
1311     }
1312     m_preloads = nullptr;
1313 }
1314
1315 void CachedResourceLoader::clearPendingPreloads()
1316 {
1317     m_pendingPreloads.clear();
1318 }
1319
1320 #if PRELOAD_DEBUG
1321 void CachedResourceLoader::printPreloadStats()
1322 {
1323     unsigned scripts = 0;
1324     unsigned scriptMisses = 0;
1325     unsigned stylesheets = 0;
1326     unsigned stylesheetMisses = 0;
1327     unsigned images = 0;
1328     unsigned imageMisses = 0;
1329     for (auto& resource : m_preloads) {
1330         if (resource->preloadResult() == CachedResource::PreloadNotReferenced)
1331             printf("!! UNREFERENCED PRELOAD %s\n", resource->url().latin1().data());
1332         else if (resource->preloadResult() == CachedResource::PreloadReferencedWhileComplete)
1333             printf("HIT COMPLETE PRELOAD %s\n", resource->url().latin1().data());
1334         else if (resource->preloadResult() == CachedResource::PreloadReferencedWhileLoading)
1335             printf("HIT LOADING PRELOAD %s\n", resource->url().latin1().data());
1336
1337         if (resource->type() == CachedResource::Script) {
1338             scripts++;
1339             if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
1340                 scriptMisses++;
1341         } else if (resource->type() == CachedResource::CSSStyleSheet) {
1342             stylesheets++;
1343             if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
1344                 stylesheetMisses++;
1345         } else {
1346             images++;
1347             if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading)
1348                 imageMisses++;
1349         }
1350
1351         if (resource->errorOccurred() && resource->preloadResult() == CachedResource::PreloadNotReferenced)
1352             MemoryCache::singleton().remove(resource);
1353
1354         resource->decreasePreloadCount();
1355     }
1356     m_preloads = nullptr;
1357
1358     if (scripts)
1359         printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
1360     if (stylesheets)
1361         printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
1362     if (images)
1363         printf("IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
1364 }
1365 #endif
1366
1367 const ResourceLoaderOptions& CachedResourceLoader::defaultCachedResourceOptions()
1368 {
1369     static ResourceLoaderOptions options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, ClientCredentialPolicy::MayAskClientForCredentials, FetchOptions::Credentials::Include, DoSecurityCheck, FetchOptions::Mode::NoCors, DoNotIncludeCertificateInfo, ContentSecurityPolicyImposition::DoPolicyCheck, DefersLoadingPolicy::AllowDefersLoading, CachingPolicy::AllowCaching);
1370     return options;
1371 }
1372
1373 bool CachedResourceLoader::isAlwaysOnLoggingAllowed() const
1374 {
1375     return m_documentLoader ? m_documentLoader->isAlwaysOnLoggingAllowed() : true;
1376 }
1377
1378 }