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-2017 Apple Inc. All rights reserved.
6 Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
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.
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.
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.
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.
28 #include "CachedResourceLoader.h"
30 #include "CachedCSSStyleSheet.h"
31 #include "CachedFont.h"
32 #include "CachedImage.h"
33 #include "CachedRawResource.h"
34 #include "CachedResourceRequest.h"
35 #include "CachedSVGDocument.h"
36 #include "CachedSVGFont.h"
37 #include "CachedScript.h"
38 #include "CachedXSLStyleSheet.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"
48 #include "DocumentLoader.h"
50 #include "FrameLoader.h"
51 #include "FrameLoaderClient.h"
52 #include "HTMLElement.h"
53 #include "HTMLFrameOwnerElement.h"
54 #include "HTTPHeaderField.h"
55 #include "LoaderStrategy.h"
56 #include "LocalizedStrings.h"
58 #include "MainFrame.h"
59 #include "MemoryCache.h"
61 #include "PingLoader.h"
62 #include "PlatformStrategies.h"
63 #include "RenderElement.h"
64 #include "ResourceLoadInfo.h"
65 #include "ResourceTiming.h"
66 #include "RuntimeEnabledFeatures.h"
67 #include "ScriptController.h"
68 #include "SecurityOrigin.h"
69 #include "SecurityPolicy.h"
70 #include "ServiceWorker.h"
72 #include "StyleSheetContents.h"
73 #include "SubresourceLoader.h"
74 #include "UserContentController.h"
75 #include "UserStyleSheet.h"
76 #include <pal/SessionID.h>
77 #include <wtf/text/CString.h>
78 #include <wtf/text/WTFString.h>
80 #if ENABLE(APPLICATION_MANIFEST)
81 #include "CachedApplicationManifest.h"
84 #if ENABLE(VIDEO_TRACK)
85 #include "CachedTextTrack.h"
88 #undef RELEASE_LOG_IF_ALLOWED
89 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), Network, "%p - CachedResourceLoader::" fmt, this, ##__VA_ARGS__)
93 // Timeout for link preloads to be used after window.onload
94 static const Seconds unusedPreloadTimeout { 3_s };
96 template <typename T, typename U>
97 static inline ResourceErrorOr<CachedResourceHandle<T>> castCachedResourceTo(ResourceErrorOr<CachedResourceHandle<U>>&& cachedResource)
100 return CachedResourceHandle<T> { static_cast<T*>(cachedResource.value().get()) };
101 return makeUnexpected(cachedResource.error());
104 static CachedResource* createResource(CachedResource::Type type, CachedResourceRequest&& request, PAL::SessionID sessionID)
107 case CachedResource::ImageResource:
108 return new CachedImage(WTFMove(request), sessionID);
109 case CachedResource::CSSStyleSheet:
110 return new CachedCSSStyleSheet(WTFMove(request), sessionID);
111 case CachedResource::Script:
112 return new CachedScript(WTFMove(request), sessionID);
113 case CachedResource::SVGDocumentResource:
114 return new CachedSVGDocument(WTFMove(request), sessionID);
115 #if ENABLE(SVG_FONTS)
116 case CachedResource::SVGFontResource:
117 return new CachedSVGFont(WTFMove(request), sessionID);
119 case CachedResource::FontResource:
120 return new CachedFont(WTFMove(request), sessionID);
121 case CachedResource::Beacon:
122 case CachedResource::MediaResource:
123 case CachedResource::RawResource:
124 case CachedResource::Icon:
125 case CachedResource::MainResource:
126 return new CachedRawResource(WTFMove(request), type, sessionID);
128 case CachedResource::XSLStyleSheet:
129 return new CachedXSLStyleSheet(WTFMove(request), sessionID);
131 #if ENABLE(LINK_PREFETCH)
132 case CachedResource::LinkPrefetch:
133 return new CachedResource(WTFMove(request), CachedResource::LinkPrefetch, sessionID);
134 case CachedResource::LinkSubresource:
135 return new CachedResource(WTFMove(request), CachedResource::LinkSubresource, sessionID);
137 #if ENABLE(VIDEO_TRACK)
138 case CachedResource::TextTrackResource:
139 return new CachedTextTrack(WTFMove(request), sessionID);
141 #if ENABLE(APPLICATION_MANIFEST)
142 case CachedResource::ApplicationManifest:
143 return new CachedApplicationManifest(WTFMove(request), sessionID);
146 ASSERT_NOT_REACHED();
150 CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader)
151 : m_document(nullptr)
152 , m_documentLoader(documentLoader)
154 , m_unusedPreloadsTimer(*this, &CachedResourceLoader::warnUnusedPreloads)
155 , m_garbageCollectDocumentResourcesTimer(*this, &CachedResourceLoader::garbageCollectDocumentResources)
156 , m_autoLoadImages(true)
157 , m_imagesEnabled(true)
158 , m_allowStaleResources(false)
162 CachedResourceLoader::~CachedResourceLoader()
164 m_documentLoader = nullptr;
165 m_document = nullptr;
167 clearPreloads(ClearPreloadsMode::ClearAllPreloads);
168 for (auto& resource : m_documentResources.values())
169 resource->setOwningCachedResourceLoader(nullptr);
171 // Make sure no requests still point to this CachedResourceLoader
172 ASSERT(m_requestCount == 0);
173 m_unusedPreloadsTimer.stop();
176 CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const
178 ASSERT(!resourceURL.isNull());
179 return cachedResource(MemoryCache::removeFragmentIdentifierIfNeeded(m_document->completeURL(resourceURL)));
182 CachedResource* CachedResourceLoader::cachedResource(const URL& url) const
184 ASSERT(!MemoryCache::shouldRemoveFragmentIdentifier(url));
185 return m_documentResources.get(url).get();
188 Frame* CachedResourceLoader::frame() const
190 return m_documentLoader ? m_documentLoader->frame() : nullptr;
193 PAL::SessionID CachedResourceLoader::sessionID() const
195 auto sessionID = PAL::SessionID::defaultSessionID();
196 if (auto* frame = this->frame()) {
197 if (auto* page = frame->page())
198 sessionID = page->sessionID();
203 ResourceErrorOr<CachedResourceHandle<CachedImage>> CachedResourceLoader::requestImage(CachedResourceRequest&& request)
205 request.setDestinationIfNotSet(FetchOptions::Destination::Image);
206 if (Frame* frame = this->frame()) {
207 if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) {
208 if (Document* document = frame->document())
209 request.upgradeInsecureRequestIfNeeded(*document);
210 URL requestURL = request.resourceRequest().url();
211 if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request, ForPreload::No))
212 PingLoader::loadImage(*frame, requestURL);
213 return CachedResourceHandle<CachedImage> { };
217 auto defer = clientDefersImage(request.resourceRequest().url()) ? DeferOption::DeferredByClient : DeferOption::NoDefer;
218 return castCachedResourceTo<CachedImage>(requestResource(CachedResource::ImageResource, WTFMove(request), ForPreload::No, defer));
221 ResourceErrorOr<CachedResourceHandle<CachedFont>> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG)
223 request.setDestinationIfNotSet(FetchOptions::Destination::Font);
224 #if ENABLE(SVG_FONTS)
226 return castCachedResourceTo<CachedFont>(requestResource(CachedResource::SVGFontResource, WTFMove(request)));
230 return castCachedResourceTo<CachedFont>(requestResource(CachedResource::FontResource, WTFMove(request)));
233 #if ENABLE(VIDEO_TRACK)
234 ResourceErrorOr<CachedResourceHandle<CachedTextTrack>> CachedResourceLoader::requestTextTrack(CachedResourceRequest&& request)
236 request.setDestinationIfNotSet(FetchOptions::Destination::Track);
237 return castCachedResourceTo<CachedTextTrack>(requestResource(CachedResource::TextTrackResource, WTFMove(request)));
241 ResourceErrorOr<CachedResourceHandle<CachedCSSStyleSheet>> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest&& request)
243 request.setDestinationIfNotSet(FetchOptions::Destination::Style);
244 return castCachedResourceTo<CachedCSSStyleSheet>(requestResource(CachedResource::CSSStyleSheet, WTFMove(request)));
247 CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest&& request)
249 request.setDestinationIfNotSet(FetchOptions::Destination::Style);
252 request.setDomainForCachePartition(*document());
254 auto& memoryCache = MemoryCache::singleton();
255 if (request.allowsCaching()) {
256 if (CachedResource* existing = memoryCache.resourceForRequest(request.resourceRequest(), sessionID())) {
257 if (is<CachedCSSStyleSheet>(*existing))
258 return downcast<CachedCSSStyleSheet>(existing);
259 memoryCache.remove(*existing);
263 request.removeFragmentIdentifierIfNeeded();
265 CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(WTFMove(request), sessionID());
267 if (userSheet->allowsCaching())
268 memoryCache.add(*userSheet);
269 // FIXME: loadResource calls setOwningCachedResourceLoader() if the resource couldn't be added to cache. Does this function need to call it, too?
271 userSheet->load(*this);
275 ResourceErrorOr<CachedResourceHandle<CachedScript>> CachedResourceLoader::requestScript(CachedResourceRequest&& request)
277 request.setDestinationIfNotSet(FetchOptions::Destination::Script);
278 return castCachedResourceTo<CachedScript>(requestResource(CachedResource::Script, WTFMove(request)));
282 ResourceErrorOr<CachedResourceHandle<CachedXSLStyleSheet>> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest&& request)
284 request.setDestinationIfNotSet(FetchOptions::Destination::Xslt);
285 return castCachedResourceTo<CachedXSLStyleSheet>(requestResource(CachedResource::XSLStyleSheet, WTFMove(request)));
289 ResourceErrorOr<CachedResourceHandle<CachedSVGDocument>> CachedResourceLoader::requestSVGDocument(CachedResourceRequest&& request)
291 return castCachedResourceTo<CachedSVGDocument>(requestResource(CachedResource::SVGDocumentResource, WTFMove(request)));
294 #if ENABLE(LINK_PREFETCH)
295 ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest&& request)
298 ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource);
299 return requestResource(type, WTFMove(request));
303 ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestMedia(CachedResourceRequest&& request)
305 // FIXME: Set destination to either audio or video.
306 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::MediaResource, WTFMove(request)));
309 ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestIcon(CachedResourceRequest&& request)
311 request.setDestinationIfNotSet(FetchOptions::Destination::Image);
312 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Icon, WTFMove(request)));
315 ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestRawResource(CachedResourceRequest&& request)
317 ASSERT(request.options().destination == FetchOptions::Destination::EmptyString || request.options().serviceWorkersMode == ServiceWorkersMode::None);
318 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::RawResource, WTFMove(request)));
321 ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestBeaconResource(CachedResourceRequest&& request)
323 ASSERT(request.options().destination == FetchOptions::Destination::EmptyString);
324 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Beacon, WTFMove(request)));
327 ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestMainResource(CachedResourceRequest&& request)
329 request.setDestinationIfNotSet(FetchOptions::Destination::Document);
330 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::MainResource, WTFMove(request)));
333 #if ENABLE(APPLICATION_MANIFEST)
334 ResourceErrorOr<CachedResourceHandle<CachedApplicationManifest>> CachedResourceLoader::requestApplicationManifest(CachedResourceRequest&& request)
336 return castCachedResourceTo<CachedApplicationManifest>(requestResource(CachedResource::ApplicationManifest, WTFMove(request)));
338 #endif // ENABLE(APPLICATION_MANIFEST)
340 static MixedContentChecker::ContentType contentTypeFromResourceType(CachedResource::Type type)
343 // https://w3c.github.io/webappsec-mixed-content/#category-optionally-blockable
344 // Editor's Draft, 11 February 2016
345 // 3.1. Optionally-blockable Content
346 case CachedResource::ImageResource:
347 case CachedResource::MediaResource:
348 return MixedContentChecker::ContentType::ActiveCanWarn;
350 case CachedResource::CSSStyleSheet:
351 case CachedResource::Script:
352 case CachedResource::FontResource:
353 return MixedContentChecker::ContentType::Active;
355 #if ENABLE(SVG_FONTS)
356 case CachedResource::SVGFontResource:
357 return MixedContentChecker::ContentType::Active;
360 case CachedResource::Beacon:
361 case CachedResource::RawResource:
362 case CachedResource::Icon:
363 case CachedResource::SVGDocumentResource:
364 return MixedContentChecker::ContentType::Active;
366 case CachedResource::XSLStyleSheet:
367 return MixedContentChecker::ContentType::Active;
370 #if ENABLE(LINK_PREFETCH)
371 case CachedResource::LinkPrefetch:
372 case CachedResource::LinkSubresource:
373 return MixedContentChecker::ContentType::Active;
376 #if ENABLE(VIDEO_TRACK)
377 case CachedResource::TextTrackResource:
378 return MixedContentChecker::ContentType::Active;
380 #if ENABLE(APPLICATION_MANIFEST)
381 case CachedResource::ApplicationManifest:
382 return MixedContentChecker::ContentType::Active;
385 ASSERT_NOT_REACHED();
386 return MixedContentChecker::ContentType::Active;
390 bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const URL& url) const
392 if (!canRequestInContentDispositionAttachmentSandbox(type, url))
396 case CachedResource::Script:
398 case CachedResource::XSLStyleSheet:
400 case CachedResource::SVGDocumentResource:
401 case CachedResource::CSSStyleSheet:
402 // These resource can inject script into the current document (Script,
403 // XSL) or exfiltrate the content of the current document (CSS).
404 if (Frame* frame = this->frame()) {
405 if (!frame->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url))
407 Frame& top = frame->tree().top();
408 if (&top != frame && !top.loader().mixedContentChecker().canRunInsecureContent(top.document()->securityOrigin(), url))
412 #if ENABLE(VIDEO_TRACK)
413 case CachedResource::TextTrackResource:
415 case CachedResource::MediaResource:
416 case CachedResource::RawResource:
417 case CachedResource::Icon:
418 case CachedResource::ImageResource:
419 #if ENABLE(SVG_FONTS)
420 case CachedResource::SVGFontResource:
422 case CachedResource::FontResource: {
423 // These resources can corrupt only the frame's pixels.
424 if (Frame* frame = this->frame()) {
425 if (!frame->loader().mixedContentChecker().canDisplayInsecureContent(m_document->securityOrigin(), contentTypeFromResourceType(type), url, MixedContentChecker::AlwaysDisplayInNonStrictMode::Yes))
427 Frame& topFrame = frame->tree().top();
428 if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), contentTypeFromResourceType(type), url))
433 case CachedResource::MainResource:
434 case CachedResource::Beacon:
435 #if ENABLE(LINK_PREFETCH)
436 case CachedResource::LinkPrefetch:
437 case CachedResource::LinkSubresource:
438 // Prefetch cannot affect the current document.
440 #if ENABLE(APPLICATION_MANIFEST)
441 case CachedResource::ApplicationManifest:
448 bool CachedResourceLoader::allowedByContentSecurityPolicy(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, ContentSecurityPolicy::RedirectResponseReceived redirectResponseReceived) const
450 if (options.contentSecurityPolicyImposition == ContentSecurityPolicyImposition::SkipPolicyCheck)
454 ASSERT(m_document->contentSecurityPolicy());
458 case CachedResource::XSLStyleSheet:
460 case CachedResource::Script:
461 if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url, redirectResponseReceived))
464 case CachedResource::CSSStyleSheet:
465 if (!m_document->contentSecurityPolicy()->allowStyleFromSource(url, redirectResponseReceived))
468 case CachedResource::SVGDocumentResource:
469 case CachedResource::Icon:
470 case CachedResource::ImageResource:
471 if (!m_document->contentSecurityPolicy()->allowImageFromSource(url, redirectResponseReceived))
474 #if ENABLE(SVG_FONTS)
475 case CachedResource::SVGFontResource:
477 case CachedResource::FontResource:
478 if (!m_document->contentSecurityPolicy()->allowFontFromSource(url, redirectResponseReceived))
481 case CachedResource::MediaResource:
482 #if ENABLE(VIDEO_TRACK)
483 case CachedResource::TextTrackResource:
485 if (!m_document->contentSecurityPolicy()->allowMediaFromSource(url, redirectResponseReceived))
488 case CachedResource::Beacon:
489 case CachedResource::RawResource:
491 #if ENABLE(APPLICATION_MANIFEST)
492 case CachedResource::ApplicationManifest:
493 if (!m_document->contentSecurityPolicy()->allowManifestFromSource(url, redirectResponseReceived))
498 ASSERT_NOT_REACHED();
504 static inline bool isSameOriginDataURL(const URL& url, const ResourceLoaderOptions& options)
506 // FIXME: Remove same-origin data URL flag since it was removed from fetch spec (https://github.com/whatwg/fetch/issues/381).
507 return url.protocolIsData() && options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set;
510 // Security checks defined in https://fetch.spec.whatwg.org/#main-fetch step 2 and 5.
511 bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const CachedResourceRequest& request, ForPreload forPreload)
513 auto& options = request.options();
515 if (document() && !document()->securityOrigin().canDisplay(url)) {
516 if (forPreload == ForPreload::No)
517 FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength());
518 LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
522 if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url) && !isSameOriginDataURL(url, options)) {
523 printAccessDeniedMessage(url);
527 if (options.mode == FetchOptions::Mode::NoCors && options.redirect != FetchOptions::Redirect::Follow) {
528 ASSERT(type != CachedResource::Type::MainResource);
529 frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, ASCIILiteral("No-Cors mode requires follow redirect mode"));
533 if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::No))
536 // SVG Images have unique security rules that prevent all subresource requests except for data urls.
537 if (type != CachedResource::MainResource && frame() && frame()->page()) {
538 if (frame()->page()->chrome().client().isSVGImageChromeClient() && !url.protocolIsData())
542 // 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.
543 // They'll still get a warning in the console about CSP blocking the load.
545 // FIXME: Should we consider whether the request is for preload here?
546 if (!checkInsecureContent(type, url))
552 // FIXME: Should we find a way to know whether the redirection is for a preload request like we do for CachedResourceLoader::canRequest?
553 bool CachedResourceLoader::canRequestAfterRedirection(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options) const
555 if (document() && !document()->securityOrigin().canDisplay(url)) {
556 FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength());
557 LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
561 // 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.
562 // But we currently allow at least data URLs to be loaded.
564 if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url)) {
565 printAccessDeniedMessage(url);
569 if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::Yes))
572 // 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.
573 // They'll still get a warning in the console about CSP blocking the load.
574 if (!checkInsecureContent(type, url))
580 bool CachedResourceLoader::updateRequestAfterRedirection(CachedResource::Type type, ResourceRequest& request, const ResourceLoaderOptions& options)
582 ASSERT(m_documentLoader);
583 if (auto* document = m_documentLoader->cachedResourceLoader().document())
584 upgradeInsecureResourceRequestIfNeeded(request, *document);
586 // FIXME: We might want to align the checks done here with the ones done in CachedResourceLoader::requestResource, content extensions blocking in particular.
588 return canRequestAfterRedirection(type, request.url(), options);
591 bool CachedResourceLoader::canRequestInContentDispositionAttachmentSandbox(CachedResource::Type type, const URL& url) const
595 // FIXME: Do we want to expand this to all resource types that the mixed content checker would consider active content?
597 case CachedResource::MainResource:
598 if (auto ownerElement = frame() ? frame()->ownerElement() : nullptr) {
599 document = &ownerElement->document();
603 case CachedResource::CSSStyleSheet:
604 document = m_document;
610 if (!document->shouldEnforceContentDispositionAttachmentSandbox() || document->securityOrigin().canRequest(url))
613 String message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from document with Content-Disposition: attachment at URL " + document->url().stringCenterEllipsizedToLength() + ".";
614 document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
618 bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource& resource, ResourceError& error)
620 if (!frame() || resource.status() != CachedResource::Cached)
623 ResourceRequest newRequest = ResourceRequest(resource.url());
624 newRequest.setInitiatorIdentifier(request.resourceRequest().initiatorIdentifier());
625 if (request.resourceRequest().hiddenFromInspector())
626 newRequest.setHiddenFromInspector(true);
627 frame()->loader().loadedResourceFromMemoryCache(resource, newRequest, error);
629 // FIXME <http://webkit.org/b/113251>: If the delegate modifies the request's
630 // URL, it is no longer appropriate to use this CachedResource.
631 return !newRequest.isNull();
634 bool CachedResourceLoader::shouldUpdateCachedResourceWithCurrentRequest(const CachedResource& resource, const CachedResourceRequest& request)
636 // 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.
637 if (resource.type() == CachedResource::Type::FontResource)
640 #if ENABLE(SVG_FONTS)
641 if (resource.type() == CachedResource::Type::SVGFontResource)
646 // Load is same-origin, we do not check for CORS.
647 if (resource.type() == CachedResource::XSLStyleSheet)
651 // FIXME: We should enable resource reuse for these resource types
652 switch (resource.type()) {
653 case CachedResource::SVGDocumentResource:
655 case CachedResource::MainResource:
657 #if ENABLE(LINK_PREFETCH)
658 case CachedResource::LinkPrefetch:
660 case CachedResource::LinkSubresource:
667 if (resource.options().mode != request.options().mode || !originsMatch(request.origin(), resource.origin()))
670 if (resource.options().redirect != request.options().redirect && resource.hasRedirections())
676 static inline bool isResourceSuitableForDirectReuse(const CachedResource& resource, const CachedResourceRequest& request)
678 // FIXME: For being loaded requests, the response tainting may not be correctly computed if the fetch mode is not the same.
679 // Even if the fetch mode is the same, we are not sure that the resource can be reused (Vary: Origin header for instance).
680 // We should find a way to improve this.
681 if (resource.status() != CachedResource::Cached)
684 // If the cached resource has not followed redirections, it is incomplete and we should not use it.
685 // Let's make sure the memory cache has no such resource.
686 ASSERT(resource.response().type() != ResourceResponse::Type::Opaqueredirect);
688 // We could support redirect modes other than Follow in case of a redirected resource.
689 // This case is rare and is not worth optimizing currently.
690 if (request.options().redirect != FetchOptions::Redirect::Follow && resource.hasRedirections())
693 // FIXME: Implement reuse of cached raw resources.
694 if (resource.type() == CachedResource::Type::RawResource || resource.type() == CachedResource::Type::MediaResource)
697 if (resource.type() == CachedResource::Beacon)
703 CachedResourceHandle<CachedResource> CachedResourceLoader::updateCachedResourceWithCurrentRequest(const CachedResource& resource, CachedResourceRequest&& request)
705 if (!isResourceSuitableForDirectReuse(resource, request)) {
706 request.setCachingPolicy(CachingPolicy::DisallowCaching);
707 return loadResource(resource.type(), WTFMove(request));
710 auto resourceHandle = createResource(resource.type(), WTFMove(request), sessionID());
711 resourceHandle->loadFrom(resource);
712 return resourceHandle;
715 static inline void logMemoryCacheResourceRequest(Frame* frame, const String& key, const String& description)
717 if (!frame || !frame->page())
719 frame->page()->diagnosticLoggingClient().logDiagnosticMessage(key, description, ShouldSample::Yes);
722 void CachedResourceLoader::prepareFetch(CachedResource::Type type, CachedResourceRequest& request)
724 // Implementing step 1 to 7 of https://fetch.spec.whatwg.org/#fetching
725 auto* document = this->document();
728 if (!request.origin())
729 request.setOrigin(document->securityOrigin());
730 #if ENABLE(SERVICE_WORKER)
731 request.setClientIdentifierIfNeeded(document->identifier());
732 if (auto* activeServiceWorker = document->activeServiceWorker())
733 request.setSelectedServiceWorkerRegistrationIdentifierIfNeeded(activeServiceWorker->registrationIdentifier());
737 request.setAcceptHeaderIfNone(type);
739 // Accept-Language value is handled in underlying port-specific code.
740 // FIXME: Decide whether to support client hints
743 void CachedResourceLoader::updateHTTPRequestHeaders(CachedResource::Type type, CachedResourceRequest& request)
745 // Implementing steps 7 to 12 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
747 // FIXME: We should reconcile handling of MainResource with other resources.
748 if (type != CachedResource::Type::MainResource) {
749 // In some cases we may try to load resources in frameless documents. Such loads always fail.
750 // FIXME: We shouldn't need to do the check on frame.
751 if (auto* frame = this->frame())
752 request.updateReferrerOriginAndUserAgentHeaders(frame->loader());
755 request.updateAccordingCacheMode();
758 ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, DeferOption defer)
760 // Entry point to https://fetch.spec.whatwg.org/#main-fetch.
761 std::unique_ptr<ResourceRequest> originalRequest;
762 if (CachedResource::shouldUsePingLoad(type))
763 originalRequest = std::make_unique<ResourceRequest>(request.resourceRequest());
765 if (Document* document = this->document())
766 request.upgradeInsecureRequestIfNeeded(*document);
768 request.updateReferrerPolicy(document() ? document()->referrerPolicy() : ReferrerPolicy::NoReferrerWhenDowngrade);
769 URL url = request.resourceRequest().url();
771 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);
773 if (!url.isValid()) {
774 RELEASE_LOG_IF_ALLOWED("requestResource: URL is invalid (frame = %p)", frame());
775 return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, url, ASCIILiteral("URL is invalid") });
778 prepareFetch(type, request);
780 // We are passing url as well as request, as request url may contain a fragment identifier.
781 if (!canRequest(type, url, request, forPreload)) {
782 RELEASE_LOG_IF_ALLOWED("requestResource: Not allowed to request resource (frame = %p)", frame());
783 return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, url, ASCIILiteral("Not allowed to request resource"), ResourceError::Type::AccessControl });
786 #if ENABLE(CONTENT_EXTENSIONS)
787 if (frame() && frame()->page() && m_documentLoader) {
788 const auto& resourceRequest = request.resourceRequest();
789 auto* page = frame()->page();
790 auto blockedStatus = page->userContentProvider().processContentExtensionRulesForLoad(resourceRequest.url(), toResourceType(type), *m_documentLoader);
791 request.applyBlockedStatus(blockedStatus, page);
792 if (blockedStatus.blockedLoad) {
793 RELEASE_LOG_IF_ALLOWED("requestResource: Resource blocked by content blocker (frame = %p)", frame());
794 if (type == CachedResource::Type::MainResource) {
795 CachedResourceHandle<CachedResource> resource = createResource(type, WTFMove(request), sessionID());
797 resource->error(CachedResource::Status::LoadError);
798 resource->setResourceError(ResourceError(ContentExtensions::WebKitContentBlockerDomain, 0, resourceRequest.url(), WEB_UI_STRING("The URL was blocked by a content blocker", "WebKitErrorBlockedByContentBlocker description")));
799 return WTFMove(resource);
801 return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, url, ASCIILiteral("Resource blocked by content blocker"), ResourceError::Type::AccessControl });
803 if (blockedStatus.madeHTTPS
804 && type == CachedResource::Type::MainResource
805 && m_documentLoader->isLoadingMainResource()) {
806 // This is to make sure the correct 'new' URL shows in the location bar.
807 m_documentLoader->frameLoader()->client().dispatchDidChangeProvisionalURL();
809 url = request.resourceRequest().url(); // The content extension could have changed it from http to https.
810 url = MemoryCache::removeFragmentIdentifierIfNeeded(url); // Might need to remove fragment identifier again.
814 if (frame() && m_documentLoader && !m_documentLoader->customHeaderFields().isEmpty()) {
815 bool sameOriginRequest = false;
816 auto requestedOrigin = SecurityOrigin::create(url);
817 if (type == CachedResource::Type::MainResource) {
818 if (frame()->isMainFrame())
819 sameOriginRequest = true;
820 else if (auto* topDocument = frame()->mainFrame().document())
821 sameOriginRequest = topDocument->securityOrigin().isSameSchemeHostPort(requestedOrigin.get());
822 } else if (document()) {
823 sameOriginRequest = document()->topDocument().securityOrigin().isSameSchemeHostPort(requestedOrigin.get())
824 && document()->securityOrigin().isSameSchemeHostPort(requestedOrigin.get());
826 if (sameOriginRequest) {
827 for (auto& field : m_documentLoader->customHeaderFields())
828 request.resourceRequest().setHTTPHeaderField(field.name(), field.value());
832 LoadTiming loadTiming;
833 loadTiming.markStartTimeAndFetchStart();
834 InitiatorContext initiatorContext = request.options().initiatorContext;
836 if (request.resourceRequest().url().protocolIsInHTTPFamily())
837 updateHTTPRequestHeaders(type, request);
839 auto& memoryCache = MemoryCache::singleton();
840 if (request.allowsCaching() && memoryCache.disabled()) {
841 if (auto handle = m_documentResources.take(url.string()))
842 handle->setOwningCachedResourceLoader(nullptr);
845 // See if we can use an existing resource from the cache.
846 CachedResourceHandle<CachedResource> resource;
848 request.setDomainForCachePartition(*document());
850 if (request.allowsCaching())
851 resource = memoryCache.resourceForRequest(request.resourceRequest(), sessionID());
853 if (resource && request.isLinkPreload() && !resource->isLinkPreload())
854 resource->setLinkPreload();
856 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheUsageKey(), resource ? DiagnosticLoggingKeys::inMemoryCacheKey() : DiagnosticLoggingKeys::notInMemoryCacheKey());
858 RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, defer);
861 memoryCache.remove(*resource);
865 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::unusedKey());
866 resource = loadResource(type, WTFMove(request));
870 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::revalidatingKey());
871 resource = revalidateResource(WTFMove(request), *resource);
875 if (shouldUpdateCachedResourceWithCurrentRequest(*resource, request)) {
876 resource = updateCachedResourceWithCurrentRequest(*resource, WTFMove(request));
877 if (resource->status() != CachedResource::Status::Cached)
881 if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, *resource, error))
882 return makeUnexpected(WTFMove(error));
883 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::usedKey());
884 loadTiming.setResponseEnd(MonotonicTime::now());
886 memoryCache.resourceAccessed(*resource);
888 if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled() && document() && !resource->isLoading()) {
889 auto resourceTiming = ResourceTiming::fromCache(url, request.initiatorName(), loadTiming, resource->response(), *request.origin());
890 if (initiatorContext == InitiatorContext::Worker) {
891 ASSERT(is<CachedRawResource>(resource.get()));
892 downcast<CachedRawResource>(resource.get())->finishedTimingForWorkerLoad(WTFMove(resourceTiming));
894 ASSERT(initiatorContext == InitiatorContext::Document);
895 m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, request.initiatorName(), frame());
896 m_resourceTimingInfo.addResourceTiming(*resource.get(), *document(), WTFMove(resourceTiming));
900 if (forPreload == ForPreload::No)
901 resource->setLoadPriority(request.priority());
906 resource->setOriginalRequest(WTFMove(originalRequest));
908 if (forPreload == ForPreload::No && resource->loader() && resource->ignoreForRequestCount()) {
909 resource->setIgnoreForRequestCount(false);
910 incrementRequestCount(*resource);
913 if ((policy != Use || resource->stillNeedsLoad()) && defer == DeferOption::NoDefer) {
914 resource->load(*this);
916 // We don't support immediate loads, but we do support immediate failure.
917 if (resource->errorOccurred()) {
918 if (resource->allowsCaching() && resource->inCache())
919 memoryCache.remove(*resource);
921 auto resourceError = resource->resourceError();
922 // Synchronous cancellations are likely due to access control.
923 if (resourceError.isNull() || resourceError.isCancellation())
924 return makeUnexpected(ResourceError { String(), 0, url, String(), ResourceError::Type::AccessControl });
925 return makeUnexpected(resourceError);
929 if (document() && !document()->loadEventFinished() && !resource->resourceRequest().url().protocolIsData())
930 m_validatedURLs.add(resource->resourceRequest().url());
932 ASSERT(resource->url() == url.string());
933 m_documentResources.set(resource->url(), resource);
934 return WTFMove(resource);
937 void CachedResourceLoader::documentDidFinishLoadEvent()
939 m_validatedURLs.clear();
941 // If m_preloads is not empty here, it's full of link preloads,
942 // as speculative preloads were cleared at DCL.
943 if (m_preloads && m_preloads->size() && !m_unusedPreloadsTimer.isActive())
944 m_unusedPreloadsTimer.startOneShot(unusedPreloadTimeout);
947 void CachedResourceLoader::stopUnusedPreloadsTimer()
949 m_unusedPreloadsTimer.stop();
952 CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(CachedResourceRequest&& request, CachedResource& resource)
954 ASSERT(resource.inCache());
955 auto& memoryCache = MemoryCache::singleton();
956 ASSERT(!memoryCache.disabled());
957 ASSERT(resource.canUseCacheValidator());
958 ASSERT(!resource.resourceToRevalidate());
959 ASSERT(resource.sessionID() == sessionID());
960 ASSERT(resource.allowsCaching());
962 CachedResourceHandle<CachedResource> newResource = createResource(resource.type(), WTFMove(request), resource.sessionID());
964 LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), &resource);
965 newResource->setResourceToRevalidate(&resource);
967 memoryCache.remove(resource);
968 memoryCache.add(*newResource);
970 if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
971 m_resourceTimingInfo.storeResourceTimingInitiatorInformation(newResource, newResource->initiatorName(), frame());
976 CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest&& request)
978 auto& memoryCache = MemoryCache::singleton();
979 ASSERT(!request.allowsCaching() || !memoryCache.resourceForRequest(request.resourceRequest(), sessionID())
980 || request.resourceRequest().cachePolicy() == DoNotUseAnyCache || request.resourceRequest().cachePolicy() == ReloadIgnoringCacheData || request.resourceRequest().cachePolicy() == RefreshAnyCacheData);
982 LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data());
984 CachedResourceHandle<CachedResource> resource = createResource(type, WTFMove(request), sessionID());
986 if (resource->allowsCaching() && !memoryCache.add(*resource))
987 resource->setOwningCachedResourceLoader(this);
989 if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
990 m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, resource->initiatorName(), frame());
995 static void logRevalidation(const String& reason, DiagnosticLoggingClient& logClient)
997 logClient.logDiagnosticMessage(DiagnosticLoggingKeys::cachedResourceRevalidationReasonKey(), reason, ShouldSample::Yes);
1000 static void logResourceRevalidationDecision(CachedResource::RevalidationDecision reason, const Frame* frame)
1002 if (!frame || !frame->page())
1004 auto& logClient = frame->page()->diagnosticLoggingClient();
1006 case CachedResource::RevalidationDecision::No:
1008 case CachedResource::RevalidationDecision::YesDueToExpired:
1009 logRevalidation(DiagnosticLoggingKeys::isExpiredKey(), logClient);
1011 case CachedResource::RevalidationDecision::YesDueToNoStore:
1012 logRevalidation(DiagnosticLoggingKeys::noStoreKey(), logClient);
1014 case CachedResource::RevalidationDecision::YesDueToNoCache:
1015 logRevalidation(DiagnosticLoggingKeys::noCacheKey(), logClient);
1017 case CachedResource::RevalidationDecision::YesDueToCachePolicy:
1018 logRevalidation(DiagnosticLoggingKeys::reloadKey(), logClient);
1023 CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, CachedResourceRequest& cachedResourceRequest, CachedResource* existingResource, ForPreload forPreload, DeferOption defer) const
1025 auto& request = cachedResourceRequest.resourceRequest();
1027 if (!existingResource)
1030 if (request.cachePolicy() == DoNotUseAnyCache || request.cachePolicy() == ReloadIgnoringCacheData)
1033 if (request.cachePolicy() == RefreshAnyCacheData)
1036 #if ENABLE(SERVICE_WORKER)
1037 // FIXME: We should validate/specify this behavior.
1038 if (cachedResourceRequest.options().serviceWorkerRegistrationIdentifier != existingResource->options().serviceWorkerRegistrationIdentifier) {
1039 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading because selected service worker differs");
1044 // We already have a preload going for this URL.
1045 if (forPreload == ForPreload::Yes && existingResource->isPreloaded())
1048 // If the same URL has been loaded as a different type, we need to reload.
1049 if (existingResource->type() != type) {
1050 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch.");
1051 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonTypeMismatchKey());
1055 if (!existingResource->varyHeaderValuesMatch(request))
1058 auto* textDecoder = existingResource->textResourceDecoder();
1059 if (textDecoder && !textDecoder->hasEqualEncodingForCharset(cachedResourceRequest.charset())) {
1060 if (!existingResource->hasUnknownEncoding())
1062 existingResource->setHasUnknownEncoding(false);
1063 existingResource->setEncoding(cachedResourceRequest.charset());
1066 // FIXME: We should use the same cache policy for all resource types. The raw resource policy is overly strict
1067 // while the normal subresource policy is too loose.
1068 if (existingResource->isMainOrMediaOrIconOrRawResource() && frame()) {
1069 bool strictPolicyDisabled = frame()->loader().isStrictRawResourceValidationPolicyDisabledForTesting();
1070 bool canReuseRawResource = strictPolicyDisabled || downcast<CachedRawResource>(*existingResource).canReuse(request);
1071 if (!canReuseRawResource)
1075 // Conditional requests should have failed canReuse check.
1076 ASSERT(!request.isConditional());
1078 // Do not load from cache if images are not enabled. The load for this image will be blocked in CachedImage::load.
1079 if (defer == DeferOption::DeferredByClient)
1082 // Don't reload resources while pasting or if cache mode allows stale resources.
1083 if (m_allowStaleResources || cachedResourceRequest.options().cache == FetchOptions::Cache::ForceCache || cachedResourceRequest.options().cache == FetchOptions::Cache::OnlyIfCached)
1086 ASSERT(cachedResourceRequest.options().cache == FetchOptions::Cache::Default || cachedResourceRequest.options().cache == FetchOptions::Cache::NoCache);
1088 // Always use preloads.
1089 if (existingResource->isPreloaded())
1092 // We can find resources that are being validated from cache only when validation is just successfully completing.
1093 if (existingResource->validationCompleting())
1095 ASSERT(!existingResource->validationInProgress());
1097 auto cachePolicy = this->cachePolicy(type, request.url());
1099 // Validate the redirect chain.
1100 bool cachePolicyIsHistoryBuffer = cachePolicy == CachePolicyHistoryBuffer;
1101 if (!existingResource->redirectChainAllowsReuse(cachePolicyIsHistoryBuffer ? ReuseExpiredRedirection : DoNotReuseExpiredRedirection)) {
1102 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to not cached or expired redirections.");
1103 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonRedirectChainKey());
1107 // CachePolicyHistoryBuffer uses the cache except if this is a main resource with "cache-control: no-store".
1108 if (cachePolicyIsHistoryBuffer) {
1109 // FIXME: Ignoring "cache-control: no-cache" for sub-resources on history navigation but not the main
1110 // resource is inconsistent. We should probably harmonize this.
1111 if (!existingResource->response().cacheControlContainsNoStore() || type != CachedResource::MainResource)
1115 // Don't reuse resources with Cache-control: no-store.
1116 if (existingResource->response().cacheControlContainsNoStore()) {
1117 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store.");
1118 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonNoStoreKey());
1122 // If credentials were sent with the previous request and won't be
1123 // with this one, or vice versa, re-fetch the resource.
1125 // This helps with the case where the server sends back
1126 // "Access-Control-Allow-Origin: *" all the time, but some of the
1127 // client's requests are made without CORS and some with.
1128 if (existingResource->resourceRequest().allowCookies() != request.allowCookies() || existingResource->options().credentials != cachedResourceRequest.options().credentials) {
1129 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to difference in credentials settings.");
1130 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonCredentialSettingsKey());
1134 // During the initial load, avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to.
1135 if (document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url()))
1138 // CachePolicyReload always reloads
1139 if (cachePolicy == CachePolicyReload) {
1140 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload.");
1141 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonReloadKey());
1145 // We'll try to reload the resource if it failed last time.
1146 if (existingResource->errorOccurred()) {
1147 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state");
1148 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonErrorKey());
1152 if (existingResource->isLoading()) {
1153 // Do not use cached main resources that are still loading because sharing
1154 // loading CachedResources in this case causes issues with regards to cancellation.
1155 // If one of the DocumentLoader clients decides to cancel the load, then the load
1156 // would be cancelled for all other DocumentLoaders as well.
1157 if (type == CachedResource::Type::MainResource)
1159 // For cached subresources that are still loading we ignore the cache policy.
1163 auto revalidationDecision = existingResource->makeRevalidationDecision(cachePolicy);
1164 logResourceRevalidationDecision(revalidationDecision, frame());
1166 // Check if the cache headers requires us to revalidate (cache expiration for example).
1167 if (revalidationDecision != CachedResource::RevalidationDecision::No) {
1168 // See if the resource has usable ETag or Last-modified headers.
1169 if (existingResource->canUseCacheValidator())
1173 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators.");
1174 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonMustRevalidateNoValidatorKey());
1181 void CachedResourceLoader::printAccessDeniedMessage(const URL& url) const
1190 if (!m_document || m_document->url().isNull())
1191 message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + '.';
1193 message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from frame with URL " + m_document->url().stringCenterEllipsizedToLength() + ". Domains, protocols and ports must match.\n";
1195 frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
1198 void CachedResourceLoader::setAutoLoadImages(bool enable)
1200 if (enable == m_autoLoadImages)
1203 m_autoLoadImages = enable;
1205 if (!m_autoLoadImages)
1208 reloadImagesIfNotDeferred();
1211 void CachedResourceLoader::setImagesEnabled(bool enable)
1213 if (enable == m_imagesEnabled)
1216 m_imagesEnabled = enable;
1218 if (!m_imagesEnabled)
1221 reloadImagesIfNotDeferred();
1224 bool CachedResourceLoader::clientDefersImage(const URL&) const
1226 return !m_imagesEnabled;
1229 bool CachedResourceLoader::shouldPerformImageLoad(const URL& url) const
1231 return m_autoLoadImages || url.protocolIsData();
1234 bool CachedResourceLoader::shouldDeferImageLoad(const URL& url) const
1236 return clientDefersImage(url) || !shouldPerformImageLoad(url);
1239 void CachedResourceLoader::reloadImagesIfNotDeferred()
1241 for (auto& resource : m_documentResources.values()) {
1242 if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && !clientDefersImage(resource->url()))
1243 downcast<CachedImage>(*resource).load(*this);
1247 CachePolicy CachedResourceLoader::cachePolicy(CachedResource::Type type, const URL& url) const
1249 Frame* frame = this->frame();
1251 return CachePolicyVerify;
1253 if (type != CachedResource::MainResource)
1254 return frame->loader().subresourceCachePolicy(url);
1256 if (Page* page = frame->page()) {
1257 if (page->isResourceCachingDisabled())
1258 return CachePolicyReload;
1261 switch (frame->loader().loadType()) {
1262 case FrameLoadType::ReloadFromOrigin:
1263 case FrameLoadType::Reload:
1264 return CachePolicyReload;
1265 case FrameLoadType::Back:
1266 case FrameLoadType::Forward:
1267 case FrameLoadType::IndexedBackForward:
1268 // Do not revalidate cached main resource on back/forward navigation.
1269 return CachePolicyHistoryBuffer;
1271 return CachePolicyVerify;
1275 void CachedResourceLoader::removeCachedResource(CachedResource& resource)
1277 ASSERT(!m_documentResources.contains(resource.url()) || m_documentResources.get(resource.url()).get() == &resource);
1278 m_documentResources.remove(resource.url());
1281 void CachedResourceLoader::loadDone(bool shouldPerformPostLoadActions)
1283 RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader);
1284 RefPtr<Document> protectDocument(m_document);
1287 frame()->loader().loadDone();
1289 if (shouldPerformPostLoadActions)
1290 performPostLoadActions();
1292 if (!m_garbageCollectDocumentResourcesTimer.isActive())
1293 m_garbageCollectDocumentResourcesTimer.startOneShot(0_s);
1296 // Garbage collecting m_documentResources is a workaround for the
1297 // CachedResourceHandles on the RHS being strong references. Ideally this
1298 // would be a weak map, however CachedResourceHandles perform additional
1299 // bookkeeping on CachedResources, so instead pseudo-GC them -- when the
1300 // reference count reaches 1, m_documentResources is the only reference, so
1301 // remove it from the map.
1302 void CachedResourceLoader::garbageCollectDocumentResources()
1304 typedef Vector<String, 10> StringVector;
1305 StringVector resourcesToDelete;
1307 for (auto& resource : m_documentResources) {
1308 if (resource.value->hasOneHandle()) {
1309 resourcesToDelete.append(resource.key);
1310 resource.value->setOwningCachedResourceLoader(nullptr);
1314 for (auto& resource : resourcesToDelete)
1315 m_documentResources.remove(resource);
1318 void CachedResourceLoader::performPostLoadActions()
1320 platformStrategies()->loaderStrategy()->servePendingRequests();
1323 void CachedResourceLoader::incrementRequestCount(const CachedResource& resource)
1325 if (resource.ignoreForRequestCount())
1331 void CachedResourceLoader::decrementRequestCount(const CachedResource& resource)
1333 if (resource.ignoreForRequestCount())
1337 ASSERT(m_requestCount > -1);
1340 ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request)
1342 if (request.charset().isEmpty() && (type == CachedResource::Script || type == CachedResource::CSSStyleSheet))
1343 request.setCharset(m_document->charset());
1345 auto resource = requestResource(type, WTFMove(request), ForPreload::Yes);
1346 if (resource && (!m_preloads || !m_preloads->contains(resource.value().get()))) {
1347 auto resourceValue = resource.value();
1348 // Fonts need special treatment since just creating the resource doesn't trigger a load.
1349 if (type == CachedResource::FontResource)
1350 downcast<CachedFont>(resourceValue.get())->beginLoadIfNeeded(*this);
1351 resourceValue->increasePreloadCount();
1354 m_preloads = std::make_unique<ListHashSet<CachedResource*>>();
1355 m_preloads->add(resourceValue.get());
1360 void CachedResourceLoader::warnUnusedPreloads()
1364 for (const auto& resource : *m_preloads) {
1365 if (resource && resource->isLinkPreload() && resource->preloadResult() == CachedResource::PreloadNotReferenced && document()) {
1366 document()->addConsoleMessage(MessageSource::Other, MessageLevel::Warning,
1367 "The resource " + resource->url().string() +
1368 " was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it wasn't preloaded for nothing.");
1373 bool CachedResourceLoader::isPreloaded(const String& urlString) const
1375 const URL& url = m_document->completeURL(urlString);
1378 for (auto& resource : *m_preloads) {
1379 if (resource->url() == url)
1386 void CachedResourceLoader::clearPreloads(ClearPreloadsMode mode)
1391 std::unique_ptr<ListHashSet<CachedResource*>> remainingLinkPreloads;
1392 for (auto* resource : *m_preloads) {
1394 if (mode == ClearPreloadsMode::ClearSpeculativePreloads && resource->isLinkPreload()) {
1395 if (!remainingLinkPreloads)
1396 remainingLinkPreloads = std::make_unique<ListHashSet<CachedResource*>>();
1397 remainingLinkPreloads->add(resource);
1400 resource->decreasePreloadCount();
1401 bool deleted = resource->deleteIfPossible();
1402 if (!deleted && resource->preloadResult() == CachedResource::PreloadNotReferenced)
1403 MemoryCache::singleton().remove(*resource);
1405 m_preloads = WTFMove(remainingLinkPreloads);
1408 const ResourceLoaderOptions& CachedResourceLoader::defaultCachedResourceOptions()
1410 static NeverDestroyed<ResourceLoaderOptions> options(SendCallbacks, SniffContent, BufferData, StoredCredentialsPolicy::Use, ClientCredentialPolicy::MayAskClientForCredentials, FetchOptions::Credentials::Include, DoSecurityCheck, FetchOptions::Mode::NoCors, DoNotIncludeCertificateInfo, ContentSecurityPolicyImposition::DoPolicyCheck, DefersLoadingPolicy::AllowDefersLoading, CachingPolicy::AllowCaching);
1414 bool CachedResourceLoader::isAlwaysOnLoggingAllowed() const
1416 return m_documentLoader ? m_documentLoader->isAlwaysOnLoggingAllowed() : true;