[Resource Timing] Gather timing information with reliable responseEnd time
[WebKit-https.git] / Source / WebCore / loader / SubresourceLoader.cpp
1 /*
2  * Copyright (C) 2006-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "SubresourceLoader.h"
31
32 #include "CachedRawResource.h"
33 #include "CachedResourceLoader.h"
34 #include "CrossOriginAccessControl.h"
35 #include "DiagnosticLoggingClient.h"
36 #include "DiagnosticLoggingKeys.h"
37 #include "Document.h"
38 #include "DocumentLoader.h"
39 #include "Frame.h"
40 #include "FrameLoader.h"
41 #include "Logging.h"
42 #include "MainFrame.h"
43 #include "MemoryCache.h"
44 #include "Page.h"
45 #include "ResourceLoadObserver.h"
46 #include "ResourceTiming.h"
47 #include "RuntimeEnabledFeatures.h"
48 #include <wtf/Ref.h>
49 #include <wtf/RefCountedLeakCounter.h>
50 #include <wtf/StdLibExtras.h>
51 #include <wtf/text/CString.h>
52
53 #if PLATFORM(IOS)
54 #include <RuntimeApplicationChecks.h>
55 #endif
56
57 #if ENABLE(CONTENT_EXTENSIONS)
58 #include "ResourceLoadInfo.h"
59 #endif
60
61 #if USE(QUICK_LOOK)
62 #include "QuickLook.h"
63 #endif
64
65 namespace WebCore {
66
67 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, subresourceLoaderCounter, ("SubresourceLoader"));
68
69 SubresourceLoader::RequestCountTracker::RequestCountTracker(CachedResourceLoader& cachedResourceLoader, const CachedResource& resource)
70     : m_cachedResourceLoader(cachedResourceLoader)
71     , m_resource(resource)
72 {
73     m_cachedResourceLoader.incrementRequestCount(m_resource);
74 }
75
76 SubresourceLoader::RequestCountTracker::~RequestCountTracker()
77 {
78     m_cachedResourceLoader.decrementRequestCount(m_resource);
79 }
80
81 SubresourceLoader::SubresourceLoader(Frame& frame, CachedResource& resource, const ResourceLoaderOptions& options)
82     : ResourceLoader(frame, options)
83     , m_resource(&resource)
84     , m_state(Uninitialized)
85     , m_requestCountTracker(std::in_place, frame.document()->cachedResourceLoader(), resource)
86 {
87 #ifndef NDEBUG
88     subresourceLoaderCounter.increment();
89 #endif
90 #if ENABLE(CONTENT_EXTENSIONS)
91     m_resourceType = toResourceType(resource.type());
92 #endif
93 }
94
95 SubresourceLoader::~SubresourceLoader()
96 {
97     ASSERT(m_state != Initialized);
98     ASSERT(reachedTerminalState());
99 #ifndef NDEBUG
100     subresourceLoaderCounter.decrement();
101 #endif
102 }
103
104 RefPtr<SubresourceLoader> SubresourceLoader::create(Frame& frame, CachedResource& resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
105 {
106     RefPtr<SubresourceLoader> subloader(adoptRef(new SubresourceLoader(frame, resource, options)));
107 #if PLATFORM(IOS)
108     if (!IOSApplication::isWebProcess()) {
109         // On iOS, do not invoke synchronous resource load delegates while resource load scheduling
110         // is disabled to avoid re-entering style selection from a different thread (see <rdar://problem/9121719>).
111         // FIXME: This should be fixed for all ports in <https://bugs.webkit.org/show_bug.cgi?id=56647>.
112         subloader->m_iOSOriginalRequest = request;
113         return subloader.release();
114     }
115 #endif
116     if (!subloader->init(request))
117         return nullptr;
118     return subloader;
119 }
120     
121 #if PLATFORM(IOS)
122 bool SubresourceLoader::startLoading()
123 {
124     ASSERT(!IOSApplication::isWebProcess());
125     if (!init(m_iOSOriginalRequest))
126         return false;
127     m_iOSOriginalRequest = ResourceRequest();
128     start();
129     return true;
130 }
131 #endif
132
133 CachedResource* SubresourceLoader::cachedResource()
134 {
135     return m_resource;
136 }
137
138 void SubresourceLoader::cancelIfNotFinishing()
139 {
140     if (m_state != Initialized)
141         return;
142
143     ResourceLoader::cancel();
144 }
145
146 bool SubresourceLoader::init(const ResourceRequest& request)
147 {
148     if (!ResourceLoader::init(request))
149         return false;
150
151     ASSERT(!reachedTerminalState());
152     m_state = Initialized;
153     m_documentLoader->addSubresourceLoader(this);
154
155     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=155633.
156     // SubresourceLoader could use the document origin as a default and set PotentiallyCrossOriginEnabled requests accordingly.
157     // This would simplify resource loader users as they would only need to set fetch mode to Cors.
158     m_origin = m_resource->origin();
159
160     return true;
161 }
162
163 bool SubresourceLoader::isSubresourceLoader()
164 {
165     return true;
166 }
167
168 void SubresourceLoader::willSendRequestInternal(ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
169 {
170     // Store the previous URL because the call to ResourceLoader::willSendRequest will modify it.
171     URL previousURL = request().url();
172     Ref<SubresourceLoader> protectedThis(*this);
173
174     if (!newRequest.url().isValid()) {
175         cancel(cannotShowURLError());
176         return;
177     }
178
179     if (newRequest.requester() != ResourceRequestBase::Requester::Main)
180         ResourceLoadObserver::sharedObserver().logSubresourceLoading(m_frame.get(), newRequest, redirectResponse);
181
182     ASSERT(!newRequest.isNull());
183     if (!redirectResponse.isNull()) {
184         if (options().redirect != FetchOptions::Redirect::Follow) {
185             if (options().redirect == FetchOptions::Redirect::Error) {
186                 cancel();
187                 return;
188             }
189
190             ResourceResponse opaqueRedirectedResponse;
191             opaqueRedirectedResponse.setURL(redirectResponse.url());
192             opaqueRedirectedResponse.setType(ResourceResponse::Type::Opaqueredirect);
193             m_resource->responseReceived(opaqueRedirectedResponse);
194             NetworkLoadMetrics emptyMetrics;
195             didFinishLoading(emptyMetrics);
196             return;
197         } else if (m_redirectCount++ >= options().maxRedirectCount) {
198             cancel(ResourceError(String(), 0, request().url(), ASCIILiteral("Too many redirections"), ResourceError::Type::General));
199             return;
200         }
201
202         // CachedResources are keyed off their original request URL.
203         // Requesting the same original URL a second time can redirect to a unique second resource.
204         // Therefore, if a redirect to a different destination URL occurs, we should no longer consider this a revalidation of the first resource.
205         // Doing so would have us reusing the resource from the first request if the second request's revalidation succeeds.
206         if (newRequest.isConditional() && m_resource->resourceToRevalidate() && newRequest.url() != m_resource->resourceToRevalidate()->response().url()) {
207             newRequest.makeUnconditional();
208             MemoryCache::singleton().revalidationFailed(*m_resource);
209             if (m_frame && m_frame->page())
210                 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
211         }
212
213         if (!m_documentLoader->cachedResourceLoader().updateRequestAfterRedirection(m_resource->type(), newRequest, options())) {
214             cancel();
215             return;
216         }
217
218         String errorDescription;
219         if (!checkRedirectionCrossOriginAccessControl(request(), redirectResponse, newRequest, errorDescription)) {
220             String errorMessage = "Cross-origin redirection to " + newRequest.url().string() + " denied by Cross-Origin Resource Sharing policy: " + errorDescription;
221             if (m_frame && m_frame->document())
222                 m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorMessage);
223             cancel(ResourceError(String(), 0, request().url(), errorMessage, ResourceError::Type::AccessControl));
224             return;
225         }
226
227         if (m_resource->isImage() && m_documentLoader->cachedResourceLoader().shouldDeferImageLoad(newRequest.url())) {
228             cancel();
229             return;
230         }
231         m_loadTiming.addRedirect(redirectResponse.url(), newRequest.url());
232         m_resource->redirectReceived(newRequest, redirectResponse);
233     }
234
235     if (newRequest.isNull() || reachedTerminalState())
236         return;
237
238     ResourceLoader::willSendRequestInternal(newRequest, redirectResponse);
239
240     if (reachedTerminalState())
241         return;
242
243     if (newRequest.isNull()) {
244         cancel();
245         return;
246     }
247
248     if (m_resource->type() == CachedResource::MainResource && !redirectResponse.isNull())
249         m_documentLoader->willContinueMainResourceLoadAfterRedirect(newRequest);
250 }
251
252 void SubresourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
253 {
254     ASSERT(m_state == Initialized);
255     Ref<SubresourceLoader> protectedThis(*this);
256     m_resource->didSendData(bytesSent, totalBytesToBeSent);
257 }
258
259 #if USE(QUICK_LOOK)
260
261 bool SubresourceLoader::shouldCreateQuickLookHandleForResponse(const ResourceResponse& response) const
262 {
263     if (m_resource->type() != CachedResource::MainResource)
264         return false;
265
266     if (m_quickLookHandle)
267         return false;
268
269     return QuickLookHandle::shouldCreateForMIMEType(response.mimeType());
270 }
271
272 #endif
273
274 void SubresourceLoader::didReceiveResponse(const ResourceResponse& response)
275 {
276     ASSERT(!response.isNull());
277     ASSERT(m_state == Initialized);
278
279 #if USE(QUICK_LOOK)
280     if (shouldCreateQuickLookHandleForResponse(response)) {
281         m_quickLookHandle = QuickLookHandle::create(*this, response);
282         return;
283     }
284 #endif
285
286     // We want redirect responses to be processed through willSendRequestInternal.
287     // The only exception is redirection with no Location headers. Or in rare circumstances,
288     // cases of too many redirects from CFNetwork (<rdar://problem/30610988>).
289 #if !PLATFORM(COCOA)
290     ASSERT(response.httpStatusCode() < 300 || response.httpStatusCode() >= 400 || response.httpStatusCode() == 304 || !response.httpHeaderField(HTTPHeaderName::Location));
291 #endif
292
293     // Reference the object in this method since the additional processing can do
294     // anything including removing the last reference to this object; one example of this is 3266216.
295     Ref<SubresourceLoader> protectedThis(*this);
296
297     if (shouldIncludeCertificateInfo())
298         response.includeCertificateInfo();
299
300     if (m_resource->resourceToRevalidate()) {
301         if (response.httpStatusCode() == 304) {
302             // 304 Not modified / Use local copy
303             // Existing resource is ok, just use it updating the expiration time.
304             m_resource->setResponse(response);
305             MemoryCache::singleton().revalidationSucceeded(*m_resource, response);
306             if (m_frame && m_frame->page())
307                 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultPass, ShouldSample::Yes);
308             if (!reachedTerminalState())
309                 ResourceLoader::didReceiveResponse(response);
310             return;
311         }
312         // Did not get 304 response, continue as a regular resource load.
313         MemoryCache::singleton().revalidationFailed(*m_resource);
314         if (m_frame && m_frame->page())
315             m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
316     }
317
318     String errorDescription;
319     if (!checkResponseCrossOriginAccessControl(response, errorDescription)) {
320         if (m_frame && m_frame->document())
321             m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorDescription);
322         cancel(ResourceError(String(), 0, request().url(), errorDescription, ResourceError::Type::AccessControl));
323         return;
324     }
325
326     m_resource->responseReceived(response);
327     if (reachedTerminalState())
328         return;
329
330     ResourceLoader::didReceiveResponse(response);
331     if (reachedTerminalState())
332         return;
333
334     // FIXME: Main resources have a different set of rules for multipart than images do.
335     // Hopefully we can merge those 2 paths.
336     if (response.isMultipart() && m_resource->type() != CachedResource::MainResource) {
337         m_loadingMultipartContent = true;
338
339         // We don't count multiParts in a CachedResourceLoader's request count
340         m_requestCountTracker = std::nullopt;
341         if (!m_resource->isImage()) {
342             cancel();
343             return;
344         }
345     }
346
347     auto* buffer = resourceData();
348     if (m_loadingMultipartContent && buffer && buffer->size()) {
349         // The resource data will change as the next part is loaded, so we need to make a copy.
350         m_resource->finishLoading(buffer->copy().ptr());
351         clearResourceData();
352         // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
353         // After the first multipart section is complete, signal to delegates that this load is "finished"
354         NetworkLoadMetrics emptyMetrics;
355         m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this);
356         didFinishLoadingOnePart(emptyMetrics);
357     }
358
359     checkForHTTPStatusCodeError();
360 }
361
362 void SubresourceLoader::didReceiveData(const char* data, unsigned length, long long encodedDataLength, DataPayloadType dataPayloadType)
363 {
364 #if USE(QUICK_LOOK)
365     if (auto quickLookHandle = m_quickLookHandle.get()) {
366         if (quickLookHandle->didReceiveData(data, length))
367             return;
368     }
369 #endif
370
371     didReceiveDataOrBuffer(data, length, nullptr, encodedDataLength, dataPayloadType);
372 }
373
374 void SubresourceLoader::didReceiveBuffer(Ref<SharedBuffer>&& buffer, long long encodedDataLength, DataPayloadType dataPayloadType)
375 {
376 #if USE(QUICK_LOOK)
377     if (auto quickLookHandle = m_quickLookHandle.get()) {
378         if (quickLookHandle->didReceiveBuffer(buffer.get()))
379             return;
380     }
381 #endif
382
383     didReceiveDataOrBuffer(nullptr, 0, WTFMove(buffer), encodedDataLength, dataPayloadType);
384 }
385
386 void SubresourceLoader::didReceiveDataOrBuffer(const char* data, int length, RefPtr<SharedBuffer>&& buffer, long long encodedDataLength, DataPayloadType dataPayloadType)
387 {
388     if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors())
389         return;
390     ASSERT(!m_resource->resourceToRevalidate());
391     ASSERT(!m_resource->errorOccurred());
392     ASSERT(m_state == Initialized);
393     // Reference the object in this method since the additional processing can do
394     // anything including removing the last reference to this object; one example of this is 3266216.
395     Ref<SubresourceLoader> protectedThis(*this);
396
397     ResourceLoader::didReceiveDataOrBuffer(data, length, buffer.copyRef(), encodedDataLength, dataPayloadType);
398
399     if (!m_loadingMultipartContent) {
400         if (auto* resourceData = this->resourceData())
401             m_resource->addDataBuffer(*resourceData);
402         else
403             m_resource->addData(buffer ? buffer->data() : data, buffer ? buffer->size() : length);
404     }
405 }
406
407 bool SubresourceLoader::checkForHTTPStatusCodeError()
408 {
409     if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors())
410         return false;
411
412     m_state = Finishing;
413     m_resource->error(CachedResource::LoadError);
414     cancel();
415     return true;
416 }
417
418 static void logResourceLoaded(Frame* frame, CachedResource::Type type)
419 {
420     if (!frame || !frame->page())
421         return;
422
423     String resourceType;
424     switch (type) {
425     case CachedResource::MainResource:
426         resourceType = DiagnosticLoggingKeys::mainResourceKey();
427         break;
428     case CachedResource::ImageResource:
429         resourceType = DiagnosticLoggingKeys::imageKey();
430         break;
431 #if ENABLE(XSLT)
432     case CachedResource::XSLStyleSheet:
433 #endif
434     case CachedResource::CSSStyleSheet:
435         resourceType = DiagnosticLoggingKeys::styleSheetKey();
436         break;
437     case CachedResource::Script:
438         resourceType = DiagnosticLoggingKeys::scriptKey();
439         break;
440     case CachedResource::FontResource:
441 #if ENABLE(SVG_FONTS)
442     case CachedResource::SVGFontResource:
443 #endif
444         resourceType = DiagnosticLoggingKeys::fontKey();
445         break;
446     case CachedResource::MediaResource:
447     case CachedResource::RawResource:
448         resourceType = DiagnosticLoggingKeys::rawKey();
449         break;
450     case CachedResource::SVGDocumentResource:
451         resourceType = DiagnosticLoggingKeys::svgDocumentKey();
452         break;
453 #if ENABLE(LINK_PREFETCH)
454     case CachedResource::LinkPrefetch:
455     case CachedResource::LinkSubresource:
456 #endif
457 #if ENABLE(VIDEO_TRACK)
458     case CachedResource::TextTrackResource:
459 #endif
460         resourceType = DiagnosticLoggingKeys::otherKey();
461         break;
462     }
463     frame->page()->diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::resourceLoadedKey(), resourceType, ShouldSample::Yes);
464 }
465
466 bool SubresourceLoader::checkResponseCrossOriginAccessControl(const ResourceResponse& response, String& errorDescription)
467 {
468     if (!m_resource->isCrossOrigin() || options().mode != FetchOptions::Mode::Cors)
469         return true;
470
471     ASSERT(m_origin);
472     return passesAccessControlCheck(response, options().allowCredentials, *m_origin, errorDescription);
473 }
474
475 bool SubresourceLoader::checkRedirectionCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest, String& errorMessage)
476 {
477     bool crossOriginFlag = m_resource->isCrossOrigin();
478     bool isNextRequestCrossOrigin = m_origin && !m_origin->canRequest(newRequest.url());
479
480     if (isNextRequestCrossOrigin)
481         m_resource->setCrossOrigin();
482
483     ASSERT(options().mode != FetchOptions::Mode::SameOrigin || !m_resource->isCrossOrigin());
484
485     if (options().mode != FetchOptions::Mode::Cors)
486         return true;
487
488     // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 8 & 9.
489     if (m_resource->isCrossOrigin() && !isValidCrossOriginRedirectionURL(newRequest.url())) {
490         errorMessage = ASCIILiteral("URL is either a non-HTTP URL or contains credentials.");
491         return false;
492     }
493
494     ASSERT(m_origin);
495     if (crossOriginFlag && !passesAccessControlCheck(redirectResponse, options().allowCredentials, *m_origin, errorMessage))
496         return false;
497
498     bool redirectingToNewOrigin = false;
499     if (m_resource->isCrossOrigin()) {
500         if (!crossOriginFlag && isNextRequestCrossOrigin)
501             redirectingToNewOrigin = true;
502         else
503             redirectingToNewOrigin = !SecurityOrigin::create(previousRequest.url())->canRequest(newRequest.url());
504     }
505
506     // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 10.
507     if (crossOriginFlag && redirectingToNewOrigin)
508         m_origin = SecurityOrigin::createUnique();
509
510     if (redirectingToNewOrigin) {
511         cleanRedirectedRequestForAccessControl(newRequest);
512         updateRequestForAccessControl(newRequest, *m_origin, options().allowCredentials);
513     }
514
515     return true;
516 }
517
518 void SubresourceLoader::didFinishLoading(const NetworkLoadMetrics& networkLoadMetrics)
519 {
520 #if USE(QUICK_LOOK)
521     if (auto quickLookHandle = m_quickLookHandle.get()) {
522         if (quickLookHandle->didFinishLoading())
523             return;
524     }
525 #endif
526
527     if (m_state != Initialized)
528         return;
529     ASSERT(!reachedTerminalState());
530     ASSERT(!m_resource->resourceToRevalidate());
531     // FIXME (129394): We should cancel the load when a decode error occurs instead of continuing the load to completion.
532     ASSERT(!m_resource->errorOccurred() || m_resource->status() == CachedResource::DecodeError || !m_resource->isLoading());
533     LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data());
534     logResourceLoaded(m_frame.get(), m_resource->type());
535
536     Ref<SubresourceLoader> protectedThis(*this);
537     CachedResourceHandle<CachedResource> protectResource(m_resource);
538
539     // FIXME: Remove this with deprecatedNetworkLoadMetrics.
540     m_loadTiming.setResponseEnd(MonotonicTime::now());
541
542 #if ENABLE(WEB_TIMING)
543     if (networkLoadMetrics.isComplete())
544         reportResourceTiming(networkLoadMetrics);
545     else {
546         // This is the legacy path for platforms (and ResourceHandle paths) that do not provide
547         // complete load metrics in didFinishLoad. In those cases, fall back to the possibility
548         // that they populated partial load timing information on the ResourceResponse.
549         reportResourceTiming(m_resource->response().deprecatedNetworkLoadMetrics());
550     }
551 #endif
552
553     m_state = Finishing;
554     m_resource->finishLoading(resourceData());
555
556     if (wasCancelled())
557         return;
558     m_resource->finish();
559     ASSERT(!reachedTerminalState());
560     didFinishLoadingOnePart(m_resource->response().deprecatedNetworkLoadMetrics());
561     notifyDone();
562     if (reachedTerminalState())
563         return;
564     releaseResources();
565 }
566
567 void SubresourceLoader::didFail(const ResourceError& error)
568 {
569 #if USE(QUICK_LOOK)
570     if (auto quickLookHandle = m_quickLookHandle.get())
571         quickLookHandle->didFail();
572 #endif
573
574     if (m_state != Initialized)
575         return;
576     ASSERT(!reachedTerminalState());
577     LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
578
579     Ref<SubresourceLoader> protectedThis(*this);
580     CachedResourceHandle<CachedResource> protectResource(m_resource);
581     m_state = Finishing;
582     if (m_resource->resourceToRevalidate())
583         MemoryCache::singleton().revalidationFailed(*m_resource);
584     m_resource->setResourceError(error);
585     if (!m_resource->isPreloaded())
586         MemoryCache::singleton().remove(*m_resource);
587     m_resource->error(CachedResource::LoadError);
588     cleanupForError(error);
589     notifyDone();
590     if (reachedTerminalState())
591         return;
592     releaseResources();
593 }
594
595 void SubresourceLoader::willCancel(const ResourceError& error)
596 {
597 #if PLATFORM(IOS)
598     // Since we defer initialization to scheduling time on iOS but
599     // CachedResourceLoader stores resources in the memory cache immediately,
600     // m_resource might be cached despite its loader not being initialized.
601     if (m_state != Initialized && m_state != Uninitialized)
602 #else
603     if (m_state != Initialized)
604 #endif
605         return;
606     ASSERT(!reachedTerminalState());
607     LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data());
608
609     Ref<SubresourceLoader> protectedThis(*this);
610 #if PLATFORM(IOS)
611     m_state = m_state == Uninitialized ? CancelledWhileInitializing : Finishing;
612 #else
613     m_state = Finishing;
614 #endif
615     auto& memoryCache = MemoryCache::singleton();
616     if (m_resource->resourceToRevalidate())
617         memoryCache.revalidationFailed(*m_resource);
618     m_resource->setResourceError(error);
619     memoryCache.remove(*m_resource);
620 }
621
622 void SubresourceLoader::didCancel(const ResourceError&)
623 {
624     if (m_state == Uninitialized)
625         return;
626
627     m_resource->cancelLoad();
628     notifyDone();
629 }
630
631 void SubresourceLoader::didRetrieveDerivedDataFromCache(const String& type, SharedBuffer& buffer)
632 {
633     if (m_state != Initialized)
634         return;
635     m_resource->didRetrieveDerivedDataFromCache(type, buffer);
636 }
637
638 void SubresourceLoader::notifyDone()
639 {
640     if (reachedTerminalState())
641         return;
642
643     m_requestCountTracker = std::nullopt;
644 #if PLATFORM(IOS)
645     m_documentLoader->cachedResourceLoader().loadDone(m_state != CancelledWhileInitializing);
646 #else
647     m_documentLoader->cachedResourceLoader().loadDone();
648 #endif
649     if (reachedTerminalState())
650         return;
651     m_documentLoader->removeSubresourceLoader(this);
652 }
653
654 void SubresourceLoader::releaseResources()
655 {
656     ASSERT(!reachedTerminalState());
657 #if PLATFORM(IOS)
658     if (m_state != Uninitialized && m_state != CancelledWhileInitializing)
659 #else
660     if (m_state != Uninitialized)
661 #endif
662         m_resource->clearLoader();
663     m_resource = nullptr;
664     ResourceLoader::releaseResources();
665 }
666
667 #if ENABLE(WEB_TIMING)
668 void SubresourceLoader::reportResourceTiming(const NetworkLoadMetrics& networkLoadMetrics)
669 {
670     if (!RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
671         return;
672
673     if (!ResourceTimingInformation::shouldAddResourceTiming(*m_resource))
674         return;
675
676     Document* document = m_documentLoader->cachedResourceLoader().document();
677     if (!document)
678         return;
679
680     SecurityOrigin& origin = m_origin ? *m_origin : document->securityOrigin();
681     ResourceTiming resourceTiming = ResourceTiming::fromLoad(*m_resource, m_resource->initiatorName(), m_loadTiming, networkLoadMetrics, origin);
682
683     // Worker resources loaded here are all CachedRawResources loaded through WorkerThreadableLoader.
684     // Pass the ResourceTiming information on so that WorkerThreadableLoader may add them to the
685     // Worker's Performance object.
686     if (options().initiatorContext == InitiatorContext::Worker) {
687         ASSERT(m_origin);
688         ASSERT(is<CachedRawResource>(m_resource));
689         downcast<CachedRawResource>(*m_resource).finishedTimingForWorkerLoad(WTFMove(resourceTiming));
690         return;
691     }
692
693     ASSERT(options().initiatorContext == InitiatorContext::Document);
694     m_documentLoader->cachedResourceLoader().resourceTimingInformation().addResourceTiming(*m_resource, *document, WTFMove(resourceTiming));
695 }
696 #endif
697
698 }