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