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