diff options
Diffstat (limited to 'Source/WebCore/loader/cache/CachedResourceLoader.cpp')
-rw-r--r-- | Source/WebCore/loader/cache/CachedResourceLoader.cpp | 1162 |
1 files changed, 756 insertions, 406 deletions
diff --git a/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp index bd38fe823..baf827eb7 100644 --- a/Source/WebCore/loader/cache/CachedResourceLoader.cpp +++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp @@ -2,7 +2,7 @@ Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) Copyright (C) 2001 Dirk Mueller (mueller@kde.org) Copyright (C) 2002 Waldo Bastian (bastian@kde.org) - Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + Copyright (C) 2004-2016 Apple Inc. All rights reserved. Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ This library is free software; you can redistribute it and/or @@ -28,18 +28,22 @@ #include "CachedResourceLoader.h" #include "CachedCSSStyleSheet.h" -#include "CachedSVGDocument.h" #include "CachedFont.h" #include "CachedImage.h" #include "CachedRawResource.h" #include "CachedResourceRequest.h" +#include "CachedSVGDocument.h" +#include "CachedSVGFont.h" #include "CachedScript.h" #include "CachedXSLStyleSheet.h" #include "Chrome.h" #include "ChromeClient.h" -#include "Console.h" +#include "ContentExtensionError.h" +#include "ContentExtensionRule.h" #include "ContentSecurityPolicy.h" #include "DOMWindow.h" +#include "DiagnosticLoggingClient.h" +#include "DiagnosticLoggingKeys.h" #include "Document.h" #include "DocumentLoader.h" #include "Frame.h" @@ -48,16 +52,26 @@ #include "HTMLElement.h" #include "HTMLFrameOwnerElement.h" #include "LoaderStrategy.h" +#include "LocalizedStrings.h" #include "Logging.h" +#include "MainFrame.h" #include "MemoryCache.h" #include "Page.h" #include "PingLoader.h" #include "PlatformStrategies.h" #include "RenderElement.h" -#include "ResourceLoadScheduler.h" +#include "ResourceLoadInfo.h" +#include "ResourceTiming.h" +#include "RuntimeEnabledFeatures.h" #include "ScriptController.h" #include "SecurityOrigin.h" +#include "SecurityPolicy.h" +#include "SessionID.h" #include "Settings.h" +#include "StyleSheetContents.h" +#include "SubresourceLoader.h" +#include "UserContentController.h" +#include "UserStyleSheet.h" #include <wtf/text/CString.h> #include <wtf/text/WTFString.h> @@ -65,56 +79,57 @@ #include "CachedTextTrack.h" #endif -#if ENABLE(RESOURCE_TIMING) -#include "Performance.h" -#endif - #define PRELOAD_DEBUG 0 +#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), Network, "%p - CachedResourceLoader::" fmt, this, ##__VA_ARGS__) + namespace WebCore { -static CachedResource* createResource(CachedResource::Type type, ResourceRequest& request, const String& charset) +static CachedResource* createResource(CachedResource::Type type, CachedResourceRequest&& request, SessionID sessionID) { switch (type) { case CachedResource::ImageResource: - return new CachedImage(request); + return new CachedImage(WTFMove(request), sessionID); case CachedResource::CSSStyleSheet: - return new CachedCSSStyleSheet(request, charset); + return new CachedCSSStyleSheet(WTFMove(request), sessionID); case CachedResource::Script: - return new CachedScript(request, charset); -#if ENABLE(SVG) + return new CachedScript(WTFMove(request), sessionID); case CachedResource::SVGDocumentResource: - return new CachedSVGDocument(request); + return new CachedSVGDocument(WTFMove(request), sessionID); +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: + return new CachedSVGFont(WTFMove(request), sessionID); #endif case CachedResource::FontResource: - return new CachedFont(request); + return new CachedFont(WTFMove(request), sessionID); + case CachedResource::MediaResource: case CachedResource::RawResource: case CachedResource::MainResource: - return new CachedRawResource(request, type); + return new CachedRawResource(WTFMove(request), type, sessionID); #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: - return new CachedXSLStyleSheet(request); + return new CachedXSLStyleSheet(WTFMove(request), sessionID); #endif #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: - return new CachedResource(request, CachedResource::LinkPrefetch); + return new CachedResource(WTFMove(request), CachedResource::LinkPrefetch, sessionID); case CachedResource::LinkSubresource: - return new CachedResource(request, CachedResource::LinkSubresource); + return new CachedResource(WTFMove(request), CachedResource::LinkSubresource, sessionID); #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: - return new CachedTextTrack(request); + return new CachedTextTrack(WTFMove(request), sessionID); #endif } ASSERT_NOT_REACHED(); - return 0; + return nullptr; } CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader) - : m_document(0) + : m_document(nullptr) , m_documentLoader(documentLoader) , m_requestCount(0) - , m_garbageCollectDocumentResourcesTimer(this, &CachedResourceLoader::garbageCollectDocumentResourcesTimerFired) + , m_garbageCollectDocumentResourcesTimer(*this, &CachedResourceLoader::garbageCollectDocumentResources) , m_autoLoadImages(true) , m_imagesEnabled(true) , m_allowStaleResources(false) @@ -123,157 +138,233 @@ CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader) CachedResourceLoader::~CachedResourceLoader() { - m_documentLoader = 0; - m_document = 0; + m_documentLoader = nullptr; + m_document = nullptr; - clearPreloads(); - DocumentResourceMap::iterator end = m_documentResources.end(); - for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) - it->value->setOwningCachedResourceLoader(0); + clearPreloads(ClearPreloadsMode::ClearAllPreloads); + for (auto& resource : m_documentResources.values()) + resource->setOwningCachedResourceLoader(nullptr); // Make sure no requests still point to this CachedResourceLoader ASSERT(m_requestCount == 0); } -CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const +CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const { - URL url = m_document->completeURL(resourceURL); - return cachedResource(url); + ASSERT(!resourceURL.isNull()); + return cachedResource(MemoryCache::removeFragmentIdentifierIfNeeded(m_document->completeURL(resourceURL))); } -CachedResource* CachedResourceLoader::cachedResource(const URL& resourceURL) const +CachedResource* CachedResourceLoader::cachedResource(const URL& url) const { - URL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL); - return m_documentResources.get(url).get(); + ASSERT(!MemoryCache::shouldRemoveFragmentIdentifier(url)); + return m_documentResources.get(url).get(); } Frame* CachedResourceLoader::frame() const { - return m_documentLoader ? m_documentLoader->frame() : 0; + return m_documentLoader ? m_documentLoader->frame() : nullptr; +} + +SessionID CachedResourceLoader::sessionID() const +{ + SessionID sessionID = SessionID::defaultSessionID(); + + if (Frame* f = frame()) + sessionID = f->page()->sessionID(); + + return sessionID; } -CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest& request) +CachedResourceHandle<CachedImage> CachedResourceLoader::requestImage(CachedResourceRequest&& request) { - if (Frame* f = frame()) { - if (f->loader().pageDismissalEventBeingDispatched() != FrameLoader::NoDismissal) { + if (Frame* frame = this->frame()) { + if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) { + if (Document* document = frame->document()) + request.upgradeInsecureRequestIfNeeded(*document); URL requestURL = request.resourceRequest().url(); - if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request.options(), request.forPreload())) - PingLoader::loadImage(f, requestURL); - return 0; + if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request, ForPreload::No)) + PingLoader::loadImage(*frame, requestURL); + return nullptr; } } - request.setDefer(clientDefersImage(request.resourceRequest().url()) ? CachedResourceRequest::DeferredByClient : CachedResourceRequest::NoDefer); - return static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, request).get()); + + auto defer = clientDefersImage(request.resourceRequest().url()) ? DeferOption::DeferredByClient : DeferOption::NoDefer; + return downcast<CachedImage>(requestResource(CachedResource::ImageResource, WTFMove(request), ForPreload::No, defer).get()); } -CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest& request) +CachedResourceHandle<CachedFont> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG) { - return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, request).get()); +#if ENABLE(SVG_FONTS) + if (isSVG) + return downcast<CachedSVGFont>(requestResource(CachedResource::SVGFontResource, WTFMove(request)).get()); +#else + UNUSED_PARAM(isSVG); +#endif + return downcast<CachedFont>(requestResource(CachedResource::FontResource, WTFMove(request)).get()); } #if ENABLE(VIDEO_TRACK) -CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest& request) +CachedResourceHandle<CachedTextTrack> CachedResourceLoader::requestTextTrack(CachedResourceRequest&& request) { - return static_cast<CachedTextTrack*>(requestResource(CachedResource::TextTrackResource, request).get()); + return downcast<CachedTextTrack>(requestResource(CachedResource::TextTrackResource, WTFMove(request)).get()); } #endif -CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest& request) +CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest&& request) { - return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, request).get()); + return downcast<CachedCSSStyleSheet>(requestResource(CachedResource::CSSStyleSheet, WTFMove(request)).get()); } -CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest& request) +CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest&& request) { - URL url = MemoryCache::removeFragmentIdentifierIfNeeded(request.resourceRequest().url()); - -#if ENABLE(CACHE_PARTITIONING) - request.mutableResourceRequest().setCachePartition(document()->topOrigin()->cachePartition()); -#endif - - if (CachedResource* existing = memoryCache()->resourceForRequest(request.resourceRequest())) { - if (existing->type() == CachedResource::CSSStyleSheet) - return static_cast<CachedCSSStyleSheet*>(existing); - memoryCache()->remove(existing); + ASSERT(document()); + request.setDomainForCachePartition(*document()); + + auto& memoryCache = MemoryCache::singleton(); + if (request.allowsCaching()) { + if (CachedResource* existing = memoryCache.resourceForRequest(request.resourceRequest(), sessionID())) { + if (is<CachedCSSStyleSheet>(*existing)) + return downcast<CachedCSSStyleSheet>(existing); + memoryCache.remove(*existing); + } } - if (url.string() != request.resourceRequest().url()) - request.mutableResourceRequest().setURL(url); - CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(request.resourceRequest(), request.charset()); + request.removeFragmentIdentifierIfNeeded(); + + CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(WTFMove(request), sessionID()); - memoryCache()->add(userSheet.get()); + if (userSheet->allowsCaching()) + memoryCache.add(*userSheet); // FIXME: loadResource calls setOwningCachedResourceLoader() if the resource couldn't be added to cache. Does this function need to call it, too? - userSheet->load(this, ResourceLoaderOptions(DoNotSendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, SkipSecurityCheck, UseDefaultOriginRestrictionsForType)); - + userSheet->load(*this); return userSheet; } -CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest& request) +CachedResourceHandle<CachedScript> CachedResourceLoader::requestScript(CachedResourceRequest&& request) { - return static_cast<CachedScript*>(requestResource(CachedResource::Script, request).get()); + return downcast<CachedScript>(requestResource(CachedResource::Script, WTFMove(request)).get()); } #if ENABLE(XSLT) -CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest& request) +CachedResourceHandle<CachedXSLStyleSheet> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest&& request) { - return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, request).get()); + return downcast<CachedXSLStyleSheet>(requestResource(CachedResource::XSLStyleSheet, WTFMove(request)).get()); } #endif -#if ENABLE(SVG) -CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest& request) +CachedResourceHandle<CachedSVGDocument> CachedResourceLoader::requestSVGDocument(CachedResourceRequest&& request) { - return static_cast<CachedSVGDocument*>(requestResource(CachedResource::SVGDocumentResource, request).get()); + return downcast<CachedSVGDocument>(requestResource(CachedResource::SVGDocumentResource, WTFMove(request)).get()); } -#endif #if ENABLE(LINK_PREFETCH) -CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest& request) +CachedResourceHandle<CachedResource> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest&& request) { ASSERT(frame()); ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource); - return requestResource(type, request); + return requestResource(type, WTFMove(request)); } #endif -CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest& request) +CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMedia(CachedResourceRequest&& request) { - return static_cast<CachedRawResource*>(requestResource(CachedResource::RawResource, request).get()); + return downcast<CachedRawResource>(requestResource(CachedResource::MediaResource, WTFMove(request)).get()); } -CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest& request) +CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestRawResource(CachedResourceRequest&& request) { - return static_cast<CachedRawResource*>(requestResource(CachedResource::MainResource, request).get()); + return downcast<CachedRawResource>(requestResource(CachedResource::RawResource, WTFMove(request)).get()); +} + +CachedResourceHandle<CachedRawResource> CachedResourceLoader::requestMainResource(CachedResourceRequest&& request) +{ + return downcast<CachedRawResource>(requestResource(CachedResource::MainResource, WTFMove(request)).get()); +} + +static MixedContentChecker::ContentType contentTypeFromResourceType(CachedResource::Type type) +{ + switch (type) { + // https://w3c.github.io/webappsec-mixed-content/#category-optionally-blockable + // Editor's Draft, 11 February 2016 + // 3.1. Optionally-blockable Content + case CachedResource::ImageResource: + case CachedResource::MediaResource: + return MixedContentChecker::ContentType::ActiveCanWarn; + + case CachedResource::CSSStyleSheet: + case CachedResource::Script: + case CachedResource::FontResource: + return MixedContentChecker::ContentType::Active; + +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: + return MixedContentChecker::ContentType::Active; +#endif + + case CachedResource::RawResource: + case CachedResource::SVGDocumentResource: + return MixedContentChecker::ContentType::Active; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + return MixedContentChecker::ContentType::Active; +#endif + +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + case CachedResource::LinkSubresource: + return MixedContentChecker::ContentType::Active; +#endif + +#if ENABLE(VIDEO_TRACK) + case CachedResource::TextTrackResource: + return MixedContentChecker::ContentType::Active; +#endif + default: + ASSERT_NOT_REACHED(); + return MixedContentChecker::ContentType::Active; + } } bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const URL& url) const { + if (!canRequestInContentDispositionAttachmentSandbox(type, url)) + return false; + switch (type) { case CachedResource::Script: #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: #endif -#if ENABLE(SVG) case CachedResource::SVGDocumentResource: -#endif case CachedResource::CSSStyleSheet: // These resource can inject script into the current document (Script, // XSL) or exfiltrate the content of the current document (CSS). - if (Frame* f = frame()) - if (!f->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url)) + if (Frame* frame = this->frame()) { + if (!frame->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url)) return false; + Frame& top = frame->tree().top(); + if (&top != frame && !top.loader().mixedContentChecker().canRunInsecureContent(top.document()->securityOrigin(), url)) + return false; + } break; #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: #endif + case CachedResource::MediaResource: case CachedResource::RawResource: case CachedResource::ImageResource: +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: +#endif case CachedResource::FontResource: { // These resources can corrupt only the frame's pixels. - if (Frame* f = frame()) { - Frame& topFrame = f->tree().top(); - if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), url)) + if (Frame* frame = this->frame()) { + if (!frame->loader().mixedContentChecker().canDisplayInsecureContent(m_document->securityOrigin(), contentTypeFromResourceType(type), url, MixedContentChecker::AlwaysDisplayInNonStrictMode::Yes)) + return false; + Frame& topFrame = frame->tree().top(); + if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), contentTypeFromResourceType(type), url)) return false; } break; @@ -289,306 +380,602 @@ bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const return true; } -bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, bool forPreload) +bool CachedResourceLoader::allowedByContentSecurityPolicy(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, ContentSecurityPolicy::RedirectResponseReceived redirectResponseReceived) const { - if (document() && !document()->securityOrigin()->canDisplay(url)) { - if (!forPreload) - FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); - LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); - return 0; - } - - // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. - bool shouldBypassMainWorldContentSecurityPolicy = (frame() && frame()->script().shouldBypassMainWorldContentSecurityPolicy()); + if (options.contentSecurityPolicyImposition == ContentSecurityPolicyImposition::SkipPolicyCheck) + return true; - // Some types of resources can be loaded only from the same origin. Other - // types of resources, like Images, Scripts, and CSS, can be loaded from - // any URL. - switch (type) { - case CachedResource::MainResource: - case CachedResource::ImageResource: - case CachedResource::CSSStyleSheet: - case CachedResource::Script: - case CachedResource::FontResource: - case CachedResource::RawResource: -#if ENABLE(LINK_PREFETCH) - case CachedResource::LinkPrefetch: - case CachedResource::LinkSubresource: -#endif -#if ENABLE(VIDEO_TRACK) - case CachedResource::TextTrackResource: -#endif - if (options.requestOriginPolicy == RestrictToSameOrigin && !m_document->securityOrigin()->canRequest(url)) { - printAccessDeniedMessage(url); - return false; - } - break; -#if ENABLE(SVG) - case CachedResource::SVGDocumentResource: -#endif -#if ENABLE(XSLT) - case CachedResource::XSLStyleSheet: - if (!m_document->securityOrigin()->canRequest(url)) { - printAccessDeniedMessage(url); - return false; - } -#endif - break; - } + ASSERT(m_document); + ASSERT(m_document->contentSecurityPolicy()); switch (type) { #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url)) - return false; - break; #endif case CachedResource::Script: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowScriptFromSource(url)) + if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url, redirectResponseReceived)) return false; - - if (frame()) { - if (!frame()->loader().client().allowScriptFromSource(frame()->settings().isScriptEnabled(), url)) - return false; - } break; case CachedResource::CSSStyleSheet: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowStyleFromSource(url)) + if (!m_document->contentSecurityPolicy()->allowStyleFromSource(url, redirectResponseReceived)) return false; break; -#if ENABLE(SVG) case CachedResource::SVGDocumentResource: -#endif case CachedResource::ImageResource: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowImageFromSource(url)) - return false; - break; - case CachedResource::FontResource: { - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowFontFromSource(url)) + if (!m_document->contentSecurityPolicy()->allowImageFromSource(url, redirectResponseReceived)) return false; break; - } - case CachedResource::MainResource: - case CachedResource::RawResource: -#if ENABLE(LINK_PREFETCH) - case CachedResource::LinkPrefetch: - case CachedResource::LinkSubresource: +#if ENABLE(SVG_FONTS) + case CachedResource::SVGFontResource: #endif + case CachedResource::FontResource: + if (!m_document->contentSecurityPolicy()->allowFontFromSource(url, redirectResponseReceived)) + return false; break; + case CachedResource::MediaResource: #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: - if (!shouldBypassMainWorldContentSecurityPolicy && !m_document->contentSecurityPolicy()->allowMediaFromSource(url)) +#endif + if (!m_document->contentSecurityPolicy()->allowMediaFromSource(url, redirectResponseReceived)) return false; break; -#endif + case CachedResource::RawResource: + return true; + default: + ASSERT_NOT_REACHED(); } + return true; +} + +static inline bool isSameOriginDataURL(const URL& url, const ResourceLoaderOptions& options) +{ + // FIXME: Remove same-origin data URL flag since it was removed from fetch spec (https://github.com/whatwg/fetch/issues/381). + return url.protocolIsData() && options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set; +} + +bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const CachedResourceRequest& request, ForPreload forPreload) +{ + auto& options = request.options(); + + if (document() && !document()->securityOrigin().canDisplay(url)) { + if (forPreload == ForPreload::No) + FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); + LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); + return false; + } + + if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url) && !isSameOriginDataURL(url, options)) { + printAccessDeniedMessage(url); + return false; + } + + if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::No)) + return false; + // SVG Images have unique security rules that prevent all subresource requests except for data urls. if (type != CachedResource::MainResource && frame() && frame()->page()) { if (frame()->page()->chrome().client().isSVGImageChromeClient() && !url.protocolIsData()) return false; } - // 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. + // 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. // They'll still get a warning in the console about CSP blocking the load. - // FIXME: Should we consider forPreload here? + // FIXME: Should we consider whether the request is for preload here? + if (!checkInsecureContent(type, url)) + return false; + + return true; +} + +// FIXME: Should we find a way to know whether the redirection is for a preload request like we do for CachedResourceLoader::canRequest? +bool CachedResourceLoader::canRequestAfterRedirection(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options) const +{ + if (document() && !document()->securityOrigin().canDisplay(url)) { + FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); + LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); + return false; + } + + // 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. + // But we currently allow at least data URLs to be loaded. + + if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url)) { + printAccessDeniedMessage(url); + return false; + } + + if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::Yes)) + return false; + + // 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. + // They'll still get a warning in the console about CSP blocking the load. if (!checkInsecureContent(type, url)) return false; return true; } -bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(CachedResource* resource) +bool CachedResourceLoader::updateRequestAfterRedirection(CachedResource::Type type, ResourceRequest& request, const ResourceLoaderOptions& options) +{ + ASSERT(m_documentLoader); + if (auto* document = m_documentLoader->cachedResourceLoader().document()) + upgradeInsecureResourceRequestIfNeeded(request, *document); + + // FIXME: We might want to align the checks done here with the ones done in CachedResourceLoader::requestResource, content extensions blocking in particular. + + return canRequestAfterRedirection(type, request.url(), options); +} + +bool CachedResourceLoader::canRequestInContentDispositionAttachmentSandbox(CachedResource::Type type, const URL& url) const +{ + Document* document; + + // FIXME: Do we want to expand this to all resource types that the mixed content checker would consider active content? + switch (type) { + case CachedResource::MainResource: + if (auto ownerElement = frame() ? frame()->ownerElement() : nullptr) { + document = &ownerElement->document(); + break; + } + return true; + case CachedResource::CSSStyleSheet: + document = m_document; + break; + default: + return true; + } + + if (!document->shouldEnforceContentDispositionAttachmentSandbox() || document->securityOrigin().canRequest(url)) + return true; + + String message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from document with Content-Disposition: attachment at URL " + document->url().stringCenterEllipsizedToLength() + "."; + document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); + return false; +} + +bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource* resource) { if (!resource || !frame() || resource->status() != CachedResource::Cached) return true; - ResourceRequest newRequest; + ResourceRequest newRequest = ResourceRequest(resource->url()); + newRequest.setInitiatorIdentifier(request.resourceRequest().initiatorIdentifier()); + if (request.resourceRequest().hiddenFromInspector()) + newRequest.setHiddenFromInspector(true); frame()->loader().loadedResourceFromMemoryCache(resource, newRequest); - + // FIXME <http://webkit.org/b/113251>: If the delegate modifies the request's // URL, it is no longer appropriate to use this CachedResource. return !newRequest.isNull(); } -CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request) +bool CachedResourceLoader::shouldUpdateCachedResourceWithCurrentRequest(const CachedResource& resource, const CachedResourceRequest& request) +{ + // 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. + if (resource.type() == CachedResource::Type::FontResource) + return false; + +#if ENABLE(SVG_FONTS) + if (resource.type() == CachedResource::Type::SVGFontResource) + return false; +#endif + +#if ENABLE(XSLT) + // Load is same-origin, we do not check for CORS. + if (resource.type() == CachedResource::XSLStyleSheet) + return false; +#endif + + // FIXME: We should enable resource reuse for these resource types + switch (resource.type()) { + case CachedResource::SVGDocumentResource: + return false; + case CachedResource::MainResource: + return false; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return false; + case CachedResource::LinkSubresource: + return false; +#endif + default: + break; + } + + if (resource.options().mode != request.options().mode || !originsMatch(request.origin(), resource.origin())) + return true; + + if (resource.options().redirect != request.options().redirect && resource.hasRedirections()) + return true; + + return false; +} + +static inline bool isResourceSuitableForDirectReuse(const CachedResource& resource, const CachedResourceRequest& request) +{ + // FIXME: For being loaded requests, the response tainting may not be correctly computed if the fetch mode is not the same. + // Even if the fetch mode is the same, we are not sure that the resource can be reused (Vary: Origin header for instance). + // We should find a way to improve this. + if (resource.status() != CachedResource::Cached) + return false; + + // If the cached resource has not followed redirections, it is incomplete and we should not use it. + // Let's make sure the memory cache has no such resource. + ASSERT(resource.response().type() != ResourceResponse::Type::Opaqueredirect); + + // We could support redirect modes other than Follow in case of a redirected resource. + // This case is rare and is not worth optimizing currently. + if (request.options().redirect != FetchOptions::Redirect::Follow && resource.hasRedirections()) + return false; + + // FIXME: Implement reuse of cached raw resources. + if (resource.type() == CachedResource::Type::RawResource || resource.type() == CachedResource::Type::MediaResource) + return false; + + return true; +} + +CachedResourceHandle<CachedResource> CachedResourceLoader::updateCachedResourceWithCurrentRequest(const CachedResource& resource, CachedResourceRequest&& request) +{ + if (!isResourceSuitableForDirectReuse(resource, request)) { + request.setCachingPolicy(CachingPolicy::DisallowCaching); + return loadResource(resource.type(), WTFMove(request)); + } + + auto resourceHandle = createResource(resource.type(), WTFMove(request), sessionID()); + resourceHandle->loadFrom(resource); + return resourceHandle; +} + +static inline void logMemoryCacheResourceRequest(Frame* frame, const String& key, const String& description) { + if (!frame || !frame->page()) + return; + frame->page()->diagnosticLoggingClient().logDiagnosticMessage(key, description, ShouldSample::Yes); +} + +void CachedResourceLoader::prepareFetch(CachedResource::Type type, CachedResourceRequest& request) +{ + // Implementing step 1 to 7 of https://fetch.spec.whatwg.org/#fetching + + if (!request.origin() && document()) + request.setOrigin(document()->securityOrigin()); + + request.setAcceptHeaderIfNone(type); + + // Accept-Language value is handled in underlying port-specific code. + // FIXME: Decide whether to support client hints +} + +void CachedResourceLoader::updateHTTPRequestHeaders(CachedResource::Type type, CachedResourceRequest& request) +{ + // Implementing steps 7 to 12 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch + + // FIXME: We should reconcile handling of MainResource with other resources. + if (type != CachedResource::Type::MainResource) { + // In some cases we may try to load resources in frameless documents. Such loads always fail. + // FIXME: We shouldn't need to do the check on frame. + if (auto* frame = this->frame()) + request.updateReferrerOriginAndUserAgentHeaders(frame->loader(), document() ? document()->referrerPolicy() : ReferrerPolicy::Default); + } + + request.updateAccordingCacheMode(); +} + +CachedResourceHandle<CachedResource> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, DeferOption defer) +{ + if (Document* document = this->document()) + request.upgradeInsecureRequestIfNeeded(*document); + URL url = request.resourceRequest().url(); - - LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority(), request.forPreload()); - - // If only the fragment identifiers differ, it is the same resource. - url = MemoryCache::removeFragmentIdentifierIfNeeded(url); - if (!url.isValid()) - return 0; + 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); - if (!canRequest(type, url, request.options(), request.forPreload())) - return 0; + if (!url.isValid()) { + RELEASE_LOG_IF_ALLOWED("requestResource: URL is invalid (frame = %p)", frame()); + return nullptr; + } - if (Frame* f = frame()) - f->loader().client().dispatchWillRequestResource(&request); + prepareFetch(type, request); + + // We are passing url as well as request, as request url may contain a fragment identifier. + if (!canRequest(type, url, request, forPreload)) { + RELEASE_LOG_IF_ALLOWED("requestResource: Not allowed to request resource (frame = %p)", frame()); + return nullptr; + } - if (memoryCache()->disabled()) { +#if ENABLE(CONTENT_EXTENSIONS) + if (frame() && frame()->mainFrame().page() && m_documentLoader) { + const auto& resourceRequest = request.resourceRequest(); + auto blockedStatus = frame()->mainFrame().page()->userContentProvider().processContentExtensionRulesForLoad(resourceRequest.url(), toResourceType(type), *m_documentLoader); + request.applyBlockedStatus(blockedStatus); + if (blockedStatus.blockedLoad) { + RELEASE_LOG_IF_ALLOWED("requestResource: Resource blocked by content blocker (frame = %p)", frame()); + if (type == CachedResource::Type::MainResource) { + auto resource = createResource(type, WTFMove(request), sessionID()); + ASSERT(resource); + resource->error(CachedResource::Status::LoadError); + resource->setResourceError(ResourceError(ContentExtensions::WebKitContentBlockerDomain, 0, resourceRequest.url(), WEB_UI_STRING("The URL was blocked by a content blocker", "WebKitErrorBlockedByContentBlocker description"))); + return resource; + } + return nullptr; + } + if (blockedStatus.madeHTTPS + && type == CachedResource::Type::MainResource + && m_documentLoader->isLoadingMainResource()) { + // This is to make sure the correct 'new' URL shows in the location bar. + m_documentLoader->frameLoader()->client().dispatchDidChangeProvisionalURL(); + } + url = request.resourceRequest().url(); // The content extension could have changed it from http to https. + url = MemoryCache::removeFragmentIdentifierIfNeeded(url); // Might need to remove fragment identifier again. + } +#endif + +#if ENABLE(WEB_TIMING) + LoadTiming loadTiming; + loadTiming.markStartTimeAndFetchStart(); + InitiatorContext initiatorContext = request.options().initiatorContext; +#endif + + if (request.resourceRequest().url().protocolIsInHTTPFamily()) + updateHTTPRequestHeaders(type, request); + + auto& memoryCache = MemoryCache::singleton(); + if (request.allowsCaching() && memoryCache.disabled()) { DocumentResourceMap::iterator it = m_documentResources.find(url.string()); if (it != m_documentResources.end()) { - it->value->setOwningCachedResourceLoader(0); + it->value->setOwningCachedResourceLoader(nullptr); m_documentResources.remove(it); } } // See if we can use an existing resource from the cache. CachedResourceHandle<CachedResource> resource; -#if ENABLE(CACHE_PARTITIONING) if (document()) - request.mutableResourceRequest().setCachePartition(document()->topOrigin()->cachePartition()); -#endif + request.setDomainForCachePartition(*document()); + + if (request.allowsCaching()) + resource = memoryCache.resourceForRequest(request.resourceRequest(), sessionID()); + + if (resource && request.isLinkPreload() && !resource->isLinkPreload()) + resource->setLinkPreload(); - resource = memoryCache()->resourceForRequest(request.resourceRequest()); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheUsageKey(), resource ? DiagnosticLoggingKeys::inMemoryCacheKey() : DiagnosticLoggingKeys::notInMemoryCacheKey()); - const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer()); + RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, defer); switch (policy) { case Reload: - memoryCache()->remove(resource.get()); + memoryCache.remove(*resource); FALLTHROUGH; case Load: - resource = loadResource(type, request, request.charset()); + if (resource) + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::unusedKey()); + resource = loadResource(type, WTFMove(request)); break; case Revalidate: - resource = revalidateResource(request, resource.get()); + if (resource) + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::revalidatingKey()); + resource = revalidateResource(WTFMove(request), *resource); break; case Use: - if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(resource.get())) - return 0; - memoryCache()->resourceAccessed(resource.get()); + ASSERT(resource); + if (shouldUpdateCachedResourceWithCurrentRequest(*resource, request)) { + resource = updateCachedResourceWithCurrentRequest(*resource, WTFMove(request)); + if (resource->status() != CachedResource::Status::Cached) + policy = Load; + } else { + if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, resource.get())) + return nullptr; + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::usedKey()); + memoryCache.resourceAccessed(*resource); +#if ENABLE(WEB_TIMING) + loadTiming.setResponseEnd(MonotonicTime::now()); + + if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) { + ResourceTiming resourceTiming = ResourceTiming::fromCache(url, request.initiatorName(), loadTiming); + if (initiatorContext == InitiatorContext::Worker) { + ASSERT(is<CachedRawResource>(resource.get())); + downcast<CachedRawResource>(resource.get())->finishedTimingForWorkerLoad(WTFMove(resourceTiming)); + } else if (document()) { + ASSERT(initiatorContext == InitiatorContext::Document); + m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, request.initiatorName(), frame()); + m_resourceTimingInfo.addResourceTiming(*resource.get(), *document(), WTFMove(resourceTiming)); + } + } +#endif + if (forPreload == ForPreload::No) + resource->setLoadPriority(request.priority()); + } break; } if (!resource) - return 0; + return nullptr; - if (!request.forPreload() || policy != Use) - resource->setLoadPriority(request.priority()); + if (forPreload == ForPreload::No && resource->loader() && resource->resourceRequest().ignoreForRequestCount()) { + resource->resourceRequest().setIgnoreForRequestCount(false); + incrementRequestCount(*resource); + } - if ((policy != Use || resource->stillNeedsLoad()) && CachedResourceRequest::NoDefer == request.defer()) { - resource->load(this, request.options()); + if ((policy != Use || resource->stillNeedsLoad()) && defer == DeferOption::NoDefer) { + resource->load(*this); // We don't support immediate loads, but we do support immediate failure. if (resource->errorOccurred()) { - if (resource->inCache()) - memoryCache()->remove(resource.get()); - return 0; + if (resource->allowsCaching() && resource->inCache()) + memoryCache.remove(*resource); + return nullptr; } } - if (!request.resourceRequest().url().protocolIsData()) - m_validatedURLs.add(request.resourceRequest().url()); + if (document() && !document()->loadEventFinished() && !resource->resourceRequest().url().protocolIsData()) + m_validatedURLs.add(resource->resourceRequest().url()); ASSERT(resource->url() == url.string()); m_documentResources.set(resource->url(), resource); return resource; } -CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(const CachedResourceRequest& request, CachedResource* resource) +void CachedResourceLoader::documentDidFinishLoadEvent() { - ASSERT(resource); - ASSERT(resource->inCache()); - ASSERT(!memoryCache()->disabled()); - ASSERT(resource->canUseCacheValidator()); - ASSERT(!resource->resourceToRevalidate()); - - // Copy the URL out of the resource to be revalidated in case it gets deleted by the remove() call below. - String url = resource->url(); - CachedResourceHandle<CachedResource> newResource = createResource(resource->type(), resource->resourceRequest(), resource->encoding()); - - LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource); - newResource->setResourceToRevalidate(resource); - - memoryCache()->remove(resource); - memoryCache()->add(newResource.get()); -#if ENABLE(RESOURCE_TIMING) - storeResourceTimingInitiatorInformation(resource, request); -#else - UNUSED_PARAM(request); + m_validatedURLs.clear(); +} + +CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(CachedResourceRequest&& request, CachedResource& resource) +{ + ASSERT(resource.inCache()); + auto& memoryCache = MemoryCache::singleton(); + ASSERT(!memoryCache.disabled()); + ASSERT(resource.canUseCacheValidator()); + ASSERT(!resource.resourceToRevalidate()); + ASSERT(resource.sessionID() == sessionID()); + ASSERT(resource.allowsCaching()); + + CachedResourceHandle<CachedResource> newResource = createResource(resource.type(), WTFMove(request), resource.sessionID()); + + LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), &resource); + newResource->setResourceToRevalidate(&resource); + + memoryCache.remove(resource); + memoryCache.add(*newResource); +#if ENABLE(WEB_TIMING) + if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) + m_resourceTimingInfo.storeResourceTimingInitiatorInformation(newResource, newResource->initiatorName(), frame()); #endif return newResource; } -CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest& request, const String& charset) +CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest&& request) { - ASSERT(!memoryCache()->resourceForRequest(request.resourceRequest())); + auto& memoryCache = MemoryCache::singleton(); + ASSERT(!request.allowsCaching() || !memoryCache.resourceForRequest(request.resourceRequest(), sessionID()) + || request.resourceRequest().cachePolicy() == DoNotUseAnyCache || request.resourceRequest().cachePolicy() == ReloadIgnoringCacheData || request.resourceRequest().cachePolicy() == RefreshAnyCacheData); LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data()); - CachedResourceHandle<CachedResource> resource = createResource(type, request.mutableResourceRequest(), charset); + CachedResourceHandle<CachedResource> resource = createResource(type, WTFMove(request), sessionID()); - if (!memoryCache()->add(resource.get())) + if (resource->allowsCaching() && !memoryCache.add(*resource)) resource->setOwningCachedResourceLoader(this); -#if ENABLE(RESOURCE_TIMING) - storeResourceTimingInitiatorInformation(resource, request); +#if ENABLE(WEB_TIMING) + if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) + m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, resource->initiatorName(), frame()); #endif return resource; } -#if ENABLE(RESOURCE_TIMING) -void CachedResourceLoader::storeResourceTimingInitiatorInformation(const CachedResourceHandle<CachedResource>& resource, const CachedResourceRequest& request) +static void logRevalidation(const String& reason, DiagnosticLoggingClient& logClient) { - if (resource->type() == CachedResource::MainResource) { - // <iframe>s should report the initial navigation requested by the parent document, but not subsequent navigations. - if (frame()->ownerElement() && m_documentLoader->frameLoader()->stateMachine()->committingFirstRealLoad()) { - InitiatorInfo info = { frame()->ownerElement()->localName(), monotonicallyIncreasingTime() }; - m_initiatorMap.add(resource.get(), info); - } - } else { - InitiatorInfo info = { request.initiatorName(), monotonicallyIncreasingTime() }; - m_initiatorMap.add(resource.get(), info); + logClient.logDiagnosticMessage(DiagnosticLoggingKeys::cachedResourceRevalidationReasonKey(), reason, ShouldSample::Yes); +} + +static void logResourceRevalidationDecision(CachedResource::RevalidationDecision reason, const Frame* frame) +{ + if (!frame || !frame->page()) + return; + auto& logClient = frame->page()->diagnosticLoggingClient(); + switch (reason) { + case CachedResource::RevalidationDecision::No: + break; + case CachedResource::RevalidationDecision::YesDueToExpired: + logRevalidation(DiagnosticLoggingKeys::isExpiredKey(), logClient); + break; + case CachedResource::RevalidationDecision::YesDueToNoStore: + logRevalidation(DiagnosticLoggingKeys::noStoreKey(), logClient); + break; + case CachedResource::RevalidationDecision::YesDueToNoCache: + logRevalidation(DiagnosticLoggingKeys::noCacheKey(), logClient); + break; + case CachedResource::RevalidationDecision::YesDueToCachePolicy: + logRevalidation(DiagnosticLoggingKeys::reloadKey(), logClient); + break; } } -#endif // ENABLE(RESOURCE_TIMING) -CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, ResourceRequest& request, bool forPreload, CachedResource* existingResource, CachedResourceRequest::DeferOption defer) const +CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, CachedResourceRequest& cachedResourceRequest, CachedResource* existingResource, ForPreload forPreload, DeferOption defer) const { + auto& request = cachedResourceRequest.resourceRequest(); + if (!existingResource) return Load; + if (request.cachePolicy() == DoNotUseAnyCache || request.cachePolicy() == ReloadIgnoringCacheData) + return Load; + + if (request.cachePolicy() == RefreshAnyCacheData) + return Reload; + // We already have a preload going for this URL. - if (forPreload && existingResource->isPreloaded()) + if (forPreload == ForPreload::Yes && existingResource->isPreloaded()) return Use; // If the same URL has been loaded as a different type, we need to reload. if (existingResource->type() != type) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonTypeMismatchKey()); return Reload; } - if (!existingResource->canReuse(request)) + if (!existingResource->varyHeaderValuesMatch(request)) return Reload; + auto* textDecoder = existingResource->textResourceDecoder(); + if (textDecoder && !textDecoder->hasEqualEncodingForCharset(cachedResourceRequest.charset())) + return Reload; + + // FIXME: We should use the same cache policy for all resource types. The raw resource policy is overly strict + // while the normal subresource policy is too loose. + if (existingResource->isMainOrMediaOrRawResource() && frame()) { + bool strictPolicyDisabled = frame()->loader().isStrictRawResourceValidationPolicyDisabledForTesting(); + bool canReuseRawResource = strictPolicyDisabled || downcast<CachedRawResource>(*existingResource).canReuse(request); + if (!canReuseRawResource) + return Reload; + } + // Conditional requests should have failed canReuse check. ASSERT(!request.isConditional()); - // Do not load from cache if images are not enabled. The load for this image will be blocked - // in CachedImage::load. - if (CachedResourceRequest::DeferredByClient == defer) + // Do not load from cache if images are not enabled. The load for this image will be blocked in CachedImage::load. + if (defer == DeferOption::DeferredByClient) return Reload; - - // Don't reload resources while pasting. - if (m_allowStaleResources) + + // Don't reload resources while pasting or if cache mode allows stale resources. + if (m_allowStaleResources || cachedResourceRequest.options().cache == FetchOptions::Cache::ForceCache || cachedResourceRequest.options().cache == FetchOptions::Cache::OnlyIfCached) return Use; - - // Alwaus use preloads. + + ASSERT(cachedResourceRequest.options().cache == FetchOptions::Cache::Default || cachedResourceRequest.options().cache == FetchOptions::Cache::NoCache); + + // Always use preloads. if (existingResource->isPreloaded()) return Use; - - // CachePolicyHistoryBuffer uses the cache no matter what. - if (cachePolicy(type) == CachePolicyHistoryBuffer) + + // We can find resources that are being validated from cache only when validation is just successfully completing. + if (existingResource->validationCompleting()) return Use; + ASSERT(!existingResource->validationInProgress()); + + // Validate the redirect chain. + bool cachePolicyIsHistoryBuffer = cachePolicy(type) == CachePolicyHistoryBuffer; + if (!existingResource->redirectChainAllowsReuse(cachePolicyIsHistoryBuffer ? ReuseExpiredRedirection : DoNotReuseExpiredRedirection)) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to not cached or expired redirections."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonRedirectChainKey()); + return Reload; + } + + // CachePolicyHistoryBuffer uses the cache except if this is a main resource with "cache-control: no-store". + if (cachePolicyIsHistoryBuffer) { + // FIXME: Ignoring "cache-control: no-cache" for sub-resources on history navigation but not the main + // resource is inconsistent. We should probably harmonize this. + if (!existingResource->response().cacheControlContainsNoStore() || type != CachedResource::MainResource) + return Use; + } // Don't reuse resources with Cache-control: no-store. if (existingResource->response().cacheControlContainsNoStore()) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonNoStoreKey()); return Reload; } @@ -600,6 +987,7 @@ CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalida // client's requests are made without CORS and some with. if (existingResource->resourceRequest().allowCookies() != request.allowCookies()) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to difference in credentials settings."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonCredentialSettingsKey()); return Reload; } @@ -610,27 +998,40 @@ CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalida // CachePolicyReload always reloads if (cachePolicy(type) == CachePolicyReload) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonReloadKey()); return Reload; } // We'll try to reload the resource if it failed last time. if (existingResource->errorOccurred()) { LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state"); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonErrorKey()); return Reload; } - - // For resources that are not yet loaded we ignore the cache policy. - if (existingResource->isLoading()) + + if (existingResource->isLoading()) { + // Do not use cached main resources that are still loading because sharing + // loading CachedResources in this case causes issues with regards to cancellation. + // If one of the DocumentLoader clients decides to cancel the load, then the load + // would be cancelled for all other DocumentLoaders as well. + if (type == CachedResource::Type::MainResource) + return Reload; + // For cached subresources that are still loading we ignore the cache policy. return Use; + } + + auto revalidationDecision = existingResource->makeRevalidationDecision(cachePolicy(type)); + logResourceRevalidationDecision(revalidationDecision, frame()); // Check if the cache headers requires us to revalidate (cache expiration for example). - if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy(type))) { + if (revalidationDecision != CachedResource::RevalidationDecision::No) { // See if the resource has usable ETag or Last-modified headers. if (existingResource->canUseCacheValidator()) return Revalidate; // No, must reload. - LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators."); + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators."); + logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonMustRevalidateNoValidatorKey()); return Reload; } @@ -651,7 +1052,7 @@ void CachedResourceLoader::printAccessDeniedMessage(const URL& url) const else message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from frame with URL " + m_document->url().stringCenterEllipsizedToLength() + ". Domains, protocols and ports must match.\n"; - frame()->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); + frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); } void CachedResourceLoader::setAutoLoadImages(bool enable) @@ -680,72 +1081,72 @@ void CachedResourceLoader::setImagesEnabled(bool enable) reloadImagesIfNotDeferred(); } -bool CachedResourceLoader::clientDefersImage(const URL& url) const +bool CachedResourceLoader::clientDefersImage(const URL&) const +{ + return !m_imagesEnabled; +} + +bool CachedResourceLoader::shouldPerformImageLoad(const URL& url) const { - return frame() && !frame()->loader().client().allowImage(m_imagesEnabled, url); + return m_autoLoadImages || url.protocolIsData(); } bool CachedResourceLoader::shouldDeferImageLoad(const URL& url) const { - return clientDefersImage(url) || !m_autoLoadImages; + return clientDefersImage(url) || !shouldPerformImageLoad(url); } void CachedResourceLoader::reloadImagesIfNotDeferred() { - DocumentResourceMap::iterator end = m_documentResources.end(); - for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { - CachedResource* resource = it->value.get(); - if (resource->type() == CachedResource::ImageResource && resource->stillNeedsLoad() && !clientDefersImage(resource->url())) - const_cast<CachedResource*>(resource)->load(this, defaultCachedResourceOptions()); + for (auto& resource : m_documentResources.values()) { + if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && !clientDefersImage(resource->url())) + downcast<CachedImage>(*resource).load(*this); } } CachePolicy CachedResourceLoader::cachePolicy(CachedResource::Type type) const { - if (!frame()) + Frame* frame = this->frame(); + if (!frame) return CachePolicyVerify; if (type != CachedResource::MainResource) - return frame()->loader().subresourceCachePolicy(); - - if (frame()->loader().loadType() == FrameLoadTypeReloadFromOrigin || frame()->loader().loadType() == FrameLoadTypeReload) + return frame->loader().subresourceCachePolicy(); + + if (Page* page = frame->page()) { + if (page->isResourceCachingDisabled()) + return CachePolicyReload; + } + + switch (frame->loader().loadType()) { + case FrameLoadType::ReloadFromOrigin: + case FrameLoadType::Reload: return CachePolicyReload; - return CachePolicyVerify; + case FrameLoadType::Back: + case FrameLoadType::Forward: + case FrameLoadType::IndexedBackForward: + // Do not revalidate cached main resource on back/forward navigation. + return CachePolicyHistoryBuffer; + default: + return CachePolicyVerify; + } } -void CachedResourceLoader::removeCachedResource(CachedResource* resource) const +void CachedResourceLoader::removeCachedResource(CachedResource& resource) { #ifndef NDEBUG - DocumentResourceMap::iterator it = m_documentResources.find(resource->url()); + DocumentResourceMap::iterator it = m_documentResources.find(resource.url()); if (it != m_documentResources.end()) - ASSERT(it->value.get() == resource); + ASSERT(it->value.get() == &resource); #endif - m_documentResources.remove(resource->url()); + m_documentResources.remove(resource.url()); } -void CachedResourceLoader::loadDone(CachedResource* resource, bool shouldPerformPostLoadActions) +void CachedResourceLoader::loadDone(bool shouldPerformPostLoadActions) { RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader); RefPtr<Document> protectDocument(m_document); -#if ENABLE(RESOURCE_TIMING) - if (resource && resource->response().isHTTP() && ((!resource->errorOccurred() && !resource->wasCanceled()) || resource->response().httpStatusCode() == 304)) { - HashMap<CachedResource*, InitiatorInfo>::iterator initiatorIt = m_initiatorMap.find(resource); - if (initiatorIt != m_initiatorMap.end()) { - ASSERT(document()); - Document* initiatorDocument = document(); - if (resource->type() == CachedResource::MainResource) - initiatorDocument = document()->parentDocument(); - ASSERT(initiatorDocument); - const InitiatorInfo& info = initiatorIt->value; - initiatorDocument->domWindow()->performance()->addResourceTiming(info.name, initiatorDocument, resource->resourceRequest(), resource->response(), info.startTime, resource->loadFinishTime()); - m_initiatorMap.remove(initiatorIt); - } - } -#else - UNUSED_PARAM(resource); -#endif // ENABLE(RESOURCE_TIMING) - if (frame()) frame()->loader().loadDone(); @@ -762,111 +1163,65 @@ void CachedResourceLoader::loadDone(CachedResource* resource, bool shouldPerform // bookkeeping on CachedResources, so instead pseudo-GC them -- when the // reference count reaches 1, m_documentResources is the only reference, so // remove it from the map. -void CachedResourceLoader::garbageCollectDocumentResourcesTimerFired(Timer<CachedResourceLoader>& timer) -{ - ASSERT_UNUSED(timer, &timer == &m_garbageCollectDocumentResourcesTimer); - garbageCollectDocumentResources(); -} - void CachedResourceLoader::garbageCollectDocumentResources() { typedef Vector<String, 10> StringVector; StringVector resourcesToDelete; - for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != m_documentResources.end(); ++it) { - if (it->value->hasOneHandle()) { - resourcesToDelete.append(it->key); - it->value->setOwningCachedResourceLoader(0); + for (auto& resource : m_documentResources) { + if (resource.value->hasOneHandle()) { + resourcesToDelete.append(resource.key); + resource.value->setOwningCachedResourceLoader(nullptr); } } - for (StringVector::const_iterator it = resourcesToDelete.begin(); it != resourcesToDelete.end(); ++it) - m_documentResources.remove(*it); + for (auto& resource : resourcesToDelete) + m_documentResources.remove(resource); } void CachedResourceLoader::performPostLoadActions() { - checkForPendingPreloads(); - - platformStrategies()->loaderStrategy()->resourceLoadScheduler()->servePendingRequests(); + platformStrategies()->loaderStrategy()->servePendingRequests(); } -void CachedResourceLoader::incrementRequestCount(const CachedResource* res) +void CachedResourceLoader::incrementRequestCount(const CachedResource& resource) { - if (res->ignoreForRequestCount()) + if (resource.ignoreForRequestCount()) return; ++m_requestCount; } -void CachedResourceLoader::decrementRequestCount(const CachedResource* res) +void CachedResourceLoader::decrementRequestCount(const CachedResource& resource) { - if (res->ignoreForRequestCount()) + if (resource.ignoreForRequestCount()) return; --m_requestCount; ASSERT(m_requestCount > -1); } -void CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest& request, const String& charset) +CachedResourceHandle<CachedResource> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request) { - // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>. - // FIXME: We should consider adding a setting to toggle aggressive preloading behavior as opposed - // to making this behavior specific to iOS. -#if !PLATFORM(IOS) - bool hasRendering = m_document->body() && m_document->renderView(); - bool canBlockParser = type == CachedResource::Script || type == CachedResource::CSSStyleSheet; - if (!hasRendering && !canBlockParser) { - // Don't preload subresources that can't block the parser before we have something to draw. - // This helps prevent preloads from delaying first display when bandwidth is limited. - PendingPreload pendingPreload = { type, request, charset }; - m_pendingPreloads.append(pendingPreload); - return; - } -#endif - requestPreload(type, request, charset); -} - -void CachedResourceLoader::checkForPendingPreloads() -{ - if (m_pendingPreloads.isEmpty() || !m_document->body() || !m_document->body()->renderer()) - return; -#if PLATFORM(IOS) - // We always preload resources on iOS. See <https://bugs.webkit.org/show_bug.cgi?id=91276>. - // So, we should never have any pending preloads. - // FIXME: We should look to avoid compiling this code entirely when building for iOS. - ASSERT_NOT_REACHED(); -#endif - while (!m_pendingPreloads.isEmpty()) { - PendingPreload preload = m_pendingPreloads.takeFirst(); - // 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). - if (!cachedResource(preload.m_request.resourceRequest().url())) - requestPreload(preload.m_type, preload.m_request, preload.m_charset); - } - m_pendingPreloads.clear(); -} + if (request.charset().isEmpty() && (type == CachedResource::Script || type == CachedResource::CSSStyleSheet)) + request.setCharset(m_document->charset()); -void CachedResourceLoader::requestPreload(CachedResource::Type type, CachedResourceRequest& request, const String& charset) -{ - String encoding; - if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) - encoding = charset.isEmpty() ? m_document->charset() : charset; - - request.setCharset(encoding); - request.setForPreload(true); - - CachedResourceHandle<CachedResource> resource = requestResource(type, request); + CachedResourceHandle<CachedResource> resource = requestResource(type, WTFMove(request), ForPreload::Yes); if (!resource || (m_preloads && m_preloads->contains(resource.get()))) - return; + return nullptr; + // Fonts need special treatment since just creating the resource doesn't trigger a load. + if (type == CachedResource::FontResource) + downcast<CachedFont>(resource.get())->beginLoadIfNeeded(*this); resource->increasePreloadCount(); if (!m_preloads) - m_preloads = adoptPtr(new ListHashSet<CachedResource*>); + m_preloads = std::make_unique<ListHashSet<CachedResource*>>(); m_preloads->add(resource.get()); #if PRELOAD_DEBUG printf("PRELOADING %s\n", resource->url().latin1().data()); #endif + return resource; } bool CachedResourceLoader::isPreloaded(const String& urlString) const @@ -874,24 +1229,15 @@ bool CachedResourceLoader::isPreloaded(const String& urlString) const const URL& url = m_document->completeURL(urlString); if (m_preloads) { - ListHashSet<CachedResource*>::iterator end = m_preloads->end(); - for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) { - CachedResource* resource = *it; + for (auto& resource : *m_preloads) { if (resource->url() == url) return true; } } - - Deque<PendingPreload>::const_iterator dequeEnd = m_pendingPreloads.end(); - for (Deque<PendingPreload>::const_iterator it = m_pendingPreloads.begin(); it != dequeEnd; ++it) { - PendingPreload pendingPreload = *it; - if (pendingPreload.m_request.resourceRequest().url() == url) - return true; - } return false; } -void CachedResourceLoader::clearPreloads() +void CachedResourceLoader::clearPreloads(ClearPreloadsMode mode) { #if PRELOAD_DEBUG printPreloadStats(); @@ -899,20 +1245,21 @@ void CachedResourceLoader::clearPreloads() if (!m_preloads) return; - ListHashSet<CachedResource*>::iterator end = m_preloads->end(); - for (ListHashSet<CachedResource*>::iterator it = m_preloads->begin(); it != end; ++it) { - CachedResource* res = *it; - res->decreasePreloadCount(); - bool deleted = res->deleteIfPossible(); - if (!deleted && res->preloadResult() == CachedResource::PreloadNotReferenced) - memoryCache()->remove(res); + std::unique_ptr<ListHashSet<CachedResource*>> remainingLinkPreloads; + for (auto* resource : *m_preloads) { + ASSERT(resource); + if (mode == ClearPreloadsMode::ClearSpeculativePreloads && resource->isLinkPreload()) { + if (!remainingLinkPreloads) + remainingLinkPreloads = std::make_unique<ListHashSet<CachedResource*>>(); + remainingLinkPreloads->add(resource); + continue; + } + resource->decreasePreloadCount(); + bool deleted = resource->deleteIfPossible(); + if (!deleted && resource->preloadResult() == CachedResource::PreloadNotReferenced) + MemoryCache::singleton().remove(*resource); } - m_preloads.clear(); -} - -void CachedResourceLoader::clearPendingPreloads() -{ - m_pendingPreloads.clear(); + m_preloads = WTFMove(remainingLinkPreloads); } #if PRELOAD_DEBUG @@ -924,37 +1271,35 @@ void CachedResourceLoader::printPreloadStats() unsigned stylesheetMisses = 0; unsigned images = 0; unsigned imageMisses = 0; - ListHashSet<CachedResource*>::iterator end = m_preloads.end(); - for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) { - CachedResource* res = *it; - if (res->preloadResult() == CachedResource::PreloadNotReferenced) - printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data()); - else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete) - printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data()); - else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading) - printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data()); - - if (res->type() == CachedResource::Script) { + for (auto& resource : m_preloads) { + if (resource->preloadResult() == CachedResource::PreloadNotReferenced) + printf("!! UNREFERENCED PRELOAD %s\n", resource->url().latin1().data()); + else if (resource->preloadResult() == CachedResource::PreloadReferencedWhileComplete) + printf("HIT COMPLETE PRELOAD %s\n", resource->url().latin1().data()); + else if (resource->preloadResult() == CachedResource::PreloadReferencedWhileLoading) + printf("HIT LOADING PRELOAD %s\n", resource->url().latin1().data()); + + if (resource->type() == CachedResource::Script) { scripts++; - if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading) scriptMisses++; - } else if (res->type() == CachedResource::CSSStyleSheet) { + } else if (resource->type() == CachedResource::CSSStyleSheet) { stylesheets++; - if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading) stylesheetMisses++; } else { images++; - if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) + if (resource->preloadResult() < CachedResource::PreloadReferencedWhileLoading) imageMisses++; } - - if (res->errorOccurred()) - memoryCache()->remove(res); - - res->decreasePreloadCount(); + + if (resource->errorOccurred() && resource->preloadResult() == CachedResource::PreloadNotReferenced) + MemoryCache::singleton().remove(resource); + + resource->decreasePreloadCount(); } - m_preloads.clear(); - + m_preloads = nullptr; + if (scripts) printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts); if (stylesheets) @@ -966,8 +1311,13 @@ void CachedResourceLoader::printPreloadStats() const ResourceLoaderOptions& CachedResourceLoader::defaultCachedResourceOptions() { - static ResourceLoaderOptions options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, DoSecurityCheck, UseDefaultOriginRestrictionsForType); + static NeverDestroyed<ResourceLoaderOptions> options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, ClientCredentialPolicy::MayAskClientForCredentials, FetchOptions::Credentials::Include, DoSecurityCheck, FetchOptions::Mode::NoCors, DoNotIncludeCertificateInfo, ContentSecurityPolicyImposition::DoPolicyCheck, DefersLoadingPolicy::AllowDefersLoading, CachingPolicy::AllowCaching); return options; } +bool CachedResourceLoader::isAlwaysOnLoggingAllowed() const +{ + return m_documentLoader ? m_documentLoader->isAlwaysOnLoggingAllowed() : true; +} + } |