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