Update Resource Load Statistics
[WebKit-https.git] / Source / WebCore / loader / SubresourceLoader.cpp
1 /*
2  * Copyright (C) 2006-2007, 2009, 2016 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 "CachedResourceLoader.h"
33 #include "CrossOriginAccessControl.h"
34 #include "DiagnosticLoggingClient.h"
35 #include "DiagnosticLoggingKeys.h"
36 #include "Document.h"
37 #include "DocumentLoader.h"
38 #include "Frame.h"
39 #include "FrameLoader.h"
40 #include "Logging.h"
41 #include "MainFrame.h"
42 #include "MemoryCache.h"
43 #include "Page.h"
44 #include "ResourceLoadObserver.h"
45 #include "RuntimeEnabledFeatures.h"
46 #include "Settings.h"
47 #include <wtf/Ref.h>
48 #include <wtf/RefCountedLeakCounter.h>
49 #include <wtf/StdLibExtras.h>
50 #include <wtf/TemporaryChange.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 namespace WebCore {
62
63 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, subresourceLoaderCounter, ("SubresourceLoader"));
64
65 SubresourceLoader::RequestCountTracker::RequestCountTracker(CachedResourceLoader& cachedResourceLoader, const CachedResource& resource)
66     : m_cachedResourceLoader(cachedResourceLoader)
67     , m_resource(resource)
68 {
69     m_cachedResourceLoader.incrementRequestCount(m_resource);
70 }
71
72 SubresourceLoader::RequestCountTracker::~RequestCountTracker()
73 {
74     m_cachedResourceLoader.decrementRequestCount(m_resource);
75 }
76
77 SubresourceLoader::SubresourceLoader(Frame& frame, CachedResource& resource, const ResourceLoaderOptions& options)
78     : ResourceLoader(frame, options)
79     , m_resource(&resource)
80     , m_loadingMultipartContent(false)
81     , m_state(Uninitialized)
82     , m_requestCountTracker(InPlace, frame.document()->cachedResourceLoader(), resource)
83 {
84 #ifndef NDEBUG
85     subresourceLoaderCounter.increment();
86 #endif
87 #if ENABLE(CONTENT_EXTENSIONS)
88     m_resourceType = toResourceType(resource.type());
89 #endif
90 }
91
92 SubresourceLoader::~SubresourceLoader()
93 {
94     ASSERT(m_state != Initialized);
95     ASSERT(reachedTerminalState());
96 #ifndef NDEBUG
97     subresourceLoaderCounter.decrement();
98 #endif
99 }
100
101 RefPtr<SubresourceLoader> SubresourceLoader::create(Frame& frame, CachedResource& resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
102 {
103     RefPtr<SubresourceLoader> subloader(adoptRef(new SubresourceLoader(frame, resource, options)));
104 #if PLATFORM(IOS)
105     if (!IOSApplication::isWebProcess()) {
106         // On iOS, do not invoke synchronous resource load delegates while resource load scheduling
107         // is disabled to avoid re-entering style selection from a different thread (see <rdar://problem/9121719>).
108         // FIXME: This should be fixed for all ports in <https://bugs.webkit.org/show_bug.cgi?id=56647>.
109         subloader->m_iOSOriginalRequest = request;
110         return subloader.release();
111     }
112 #endif
113     if (!subloader->init(request))
114         return nullptr;
115     return subloader;
116 }
117     
118 #if PLATFORM(IOS)
119 bool SubresourceLoader::startLoading()
120 {
121     ASSERT(!IOSApplication::isWebProcess());
122     if (!init(m_iOSOriginalRequest))
123         return false;
124     m_iOSOriginalRequest = ResourceRequest();
125     start();
126     return true;
127 }
128 #endif
129
130 CachedResource* SubresourceLoader::cachedResource()
131 {
132     return m_resource;
133 }
134
135 void SubresourceLoader::cancelIfNotFinishing()
136 {
137     if (m_state != Initialized)
138         return;
139
140     ResourceLoader::cancel();
141 }
142
143 bool SubresourceLoader::init(const ResourceRequest& request)
144 {
145     if (!ResourceLoader::init(request))
146         return false;
147
148     ASSERT(!reachedTerminalState());
149     m_state = Initialized;
150     m_documentLoader->addSubresourceLoader(this);
151
152     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=155633.
153     // SubresourceLoader could use the document origin as a default and set PotentiallyCrossOriginEnabled requests accordingly.
154     // This would simplify resource loader users as they would only need to set fetch mode to Cors.
155     m_origin = m_resource->origin();
156
157     return true;
158 }
159
160 bool SubresourceLoader::isSubresourceLoader()
161 {
162     return true;
163 }
164
165 void SubresourceLoader::willSendRequestInternal(ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
166 {
167     // Store the previous URL because the call to ResourceLoader::willSendRequest will modify it.
168     URL previousURL = request().url();
169     Ref<SubresourceLoader> protectedThis(*this);
170
171     if (!newRequest.url().isValid()) {
172         cancel(cannotShowURLError());
173         return;
174     }
175
176     if (newRequest.requester() != ResourceRequestBase::Requester::Main)
177         ResourceLoadObserver::sharedObserver().logSubresourceLoading(m_frame.get(), newRequest, redirectResponse);
178
179     ASSERT(!newRequest.isNull());
180     if (!redirectResponse.isNull()) {
181         if (options().redirect != FetchOptions::Redirect::Follow) {
182             if (options().redirect == FetchOptions::Redirect::Error) {
183                 cancel();
184                 return;
185             }
186
187             ResourceResponse opaqueRedirectedResponse;
188             opaqueRedirectedResponse.setURL(redirectResponse.url());
189             opaqueRedirectedResponse.setType(ResourceResponse::Type::Opaqueredirect);
190             m_resource->responseReceived(opaqueRedirectedResponse);
191             didFinishLoading(currentTime());
192             return;
193         }
194
195         // CachedResources are keyed off their original request URL.
196         // Requesting the same original URL a second time can redirect to a unique second resource.
197         // Therefore, if a redirect to a different destination URL occurs, we should no longer consider this a revalidation of the first resource.
198         // Doing so would have us reusing the resource from the first request if the second request's revalidation succeeds.
199         if (newRequest.isConditional() && m_resource->resourceToRevalidate() && newRequest.url() != m_resource->resourceToRevalidate()->response().url()) {
200             newRequest.makeUnconditional();
201             MemoryCache::singleton().revalidationFailed(*m_resource);
202             if (m_frame && m_frame->page())
203                 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
204         }
205
206         if (!m_documentLoader->cachedResourceLoader().canRequestAfterRedirection(m_resource->type(), newRequest.url(), options())) {
207             cancel();
208             return;
209         }
210
211         String errorDescription;
212         if (!checkRedirectionCrossOriginAccessControl(request(), redirectResponse, newRequest, errorDescription)) {
213             String errorMessage = "Cross-origin redirection to " + newRequest.url().string() + " denied by Cross-Origin Resource Sharing policy: " + errorDescription;
214             if (m_frame && m_frame->document())
215                 m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorMessage);
216             cancel(ResourceError(String(), 0, request().url(), errorMessage, ResourceError::Type::AccessControl));
217             return;
218         }
219
220         if (m_resource->isImage() && m_documentLoader->cachedResourceLoader().shouldDeferImageLoad(newRequest.url())) {
221             cancel();
222             return;
223         }
224         m_loadTiming.addRedirect(redirectResponse.url(), newRequest.url());
225         m_resource->redirectReceived(newRequest, redirectResponse);
226     }
227
228     if (newRequest.isNull() || reachedTerminalState())
229         return;
230
231     ResourceLoader::willSendRequestInternal(newRequest, redirectResponse);
232     if (newRequest.isNull())
233         cancel();
234 }
235
236 void SubresourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
237 {
238     ASSERT(m_state == Initialized);
239     Ref<SubresourceLoader> protectedThis(*this);
240     m_resource->didSendData(bytesSent, totalBytesToBeSent);
241 }
242
243 void SubresourceLoader::didReceiveResponse(const ResourceResponse& response)
244 {
245     ASSERT(!response.isNull());
246     ASSERT(m_state == Initialized);
247
248     // We want redirect responses to be processed through willSendRequestInternal. The only exception is redirection with no Location headers.
249     ASSERT(response.httpStatusCode() < 300 || response.httpStatusCode() >= 400 || response.httpStatusCode() == 304 || !response.httpHeaderField(HTTPHeaderName::Location));
250
251     // Reference the object in this method since the additional processing can do
252     // anything including removing the last reference to this object; one example of this is 3266216.
253     Ref<SubresourceLoader> protectedThis(*this);
254
255     if (shouldIncludeCertificateInfo())
256         response.includeCertificateInfo();
257
258     if (response.isHttpVersion0_9()) {
259         if (m_frame) {
260             String message = "Sandboxing '" + response.url().string() + "' because it is using HTTP/0.9.";
261             m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, identifier());
262             frameLoader()->forceSandboxFlags(SandboxScripts | SandboxPlugins);
263         }
264     }
265
266     if (m_resource->resourceToRevalidate()) {
267         if (response.httpStatusCode() == 304) {
268             // 304 Not modified / Use local copy
269             // Existing resource is ok, just use it updating the expiration time.
270             m_resource->setResponse(response);
271             MemoryCache::singleton().revalidationSucceeded(*m_resource, response);
272             if (m_frame && m_frame->page())
273                 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultPass, ShouldSample::Yes);
274             if (!reachedTerminalState())
275                 ResourceLoader::didReceiveResponse(response);
276             return;
277         }
278         // Did not get 304 response, continue as a regular resource load.
279         MemoryCache::singleton().revalidationFailed(*m_resource);
280         if (m_frame && m_frame->page())
281             m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
282     }
283
284     String errorDescription;
285     if (!checkResponseCrossOriginAccessControl(response, errorDescription)) {
286         if (m_frame && m_frame->document())
287             m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorDescription);
288         cancel(ResourceError(String(), 0, request().url(), errorDescription, ResourceError::Type::AccessControl));
289         return;
290     }
291
292     m_resource->responseReceived(response);
293     if (reachedTerminalState())
294         return;
295
296     ResourceLoader::didReceiveResponse(response);
297     if (reachedTerminalState())
298         return;
299
300     // FIXME: Main resources have a different set of rules for multipart than images do.
301     // Hopefully we can merge those 2 paths.
302     if (response.isMultipart() && m_resource->type() != CachedResource::MainResource) {
303         m_loadingMultipartContent = true;
304
305         // We don't count multiParts in a CachedResourceLoader's request count
306         m_requestCountTracker = Nullopt;
307         if (!m_resource->isImage()) {
308             cancel();
309             return;
310         }
311     }
312
313     auto* buffer = resourceData();
314     if (m_loadingMultipartContent && buffer && buffer->size()) {
315         // The resource data will change as the next part is loaded, so we need to make a copy.
316         m_resource->finishLoading(buffer->copy().ptr());
317         clearResourceData();
318         // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
319         // After the first multipart section is complete, signal to delegates that this load is "finished"
320         m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this);
321         didFinishLoadingOnePart(0);
322     }
323
324     checkForHTTPStatusCodeError();
325 }
326
327 void SubresourceLoader::didReceiveData(const char* data, unsigned length, long long encodedDataLength, DataPayloadType dataPayloadType)
328 {
329     didReceiveDataOrBuffer(data, length, nullptr, encodedDataLength, dataPayloadType);
330 }
331
332 void SubresourceLoader::didReceiveBuffer(Ref<SharedBuffer>&& buffer, long long encodedDataLength, DataPayloadType dataPayloadType)
333 {
334     didReceiveDataOrBuffer(nullptr, 0, WTFMove(buffer), encodedDataLength, dataPayloadType);
335 }
336
337 void SubresourceLoader::didReceiveDataOrBuffer(const char* data, int length, RefPtr<SharedBuffer>&& prpBuffer, long long encodedDataLength, DataPayloadType dataPayloadType)
338 {
339     if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors())
340         return;
341     ASSERT(!m_resource->resourceToRevalidate());
342     ASSERT(!m_resource->errorOccurred());
343     ASSERT(m_state == Initialized);
344     // Reference the object in this method since the additional processing can do
345     // anything including removing the last reference to this object; one example of this is 3266216.
346     Ref<SubresourceLoader> protectedThis(*this);
347     RefPtr<SharedBuffer> buffer = prpBuffer;
348     
349     ResourceLoader::didReceiveDataOrBuffer(data, length, WTFMove(buffer), encodedDataLength, dataPayloadType);
350
351     if (!m_loadingMultipartContent) {
352         if (auto* resourceData = this->resourceData())
353             m_resource->addDataBuffer(*resourceData);
354         else
355             m_resource->addData(buffer ? buffer->data() : data, buffer ? buffer->size() : length);
356     }
357 }
358
359 bool SubresourceLoader::checkForHTTPStatusCodeError()
360 {
361     if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors())
362         return false;
363
364     m_state = Finishing;
365     m_resource->error(CachedResource::LoadError);
366     cancel();
367     return true;
368 }
369
370 static void logResourceLoaded(Frame* frame, CachedResource::Type type)
371 {
372     if (!frame || !frame->page())
373         return;
374
375     String resourceType;
376     switch (type) {
377     case CachedResource::MainResource:
378         resourceType = DiagnosticLoggingKeys::mainResourceKey();
379         break;
380     case CachedResource::ImageResource:
381         resourceType = DiagnosticLoggingKeys::imageKey();
382         break;
383 #if ENABLE(XSLT)
384     case CachedResource::XSLStyleSheet:
385 #endif
386     case CachedResource::CSSStyleSheet:
387         resourceType = DiagnosticLoggingKeys::styleSheetKey();
388         break;
389     case CachedResource::Script:
390         resourceType = DiagnosticLoggingKeys::scriptKey();
391         break;
392     case CachedResource::FontResource:
393 #if ENABLE(SVG_FONTS)
394     case CachedResource::SVGFontResource:
395 #endif
396         resourceType = DiagnosticLoggingKeys::fontKey();
397         break;
398     case CachedResource::MediaResource:
399     case CachedResource::RawResource:
400         resourceType = DiagnosticLoggingKeys::rawKey();
401         break;
402     case CachedResource::SVGDocumentResource:
403         resourceType = DiagnosticLoggingKeys::svgDocumentKey();
404         break;
405 #if ENABLE(LINK_PREFETCH)
406     case CachedResource::LinkPrefetch:
407     case CachedResource::LinkSubresource:
408 #endif
409 #if ENABLE(VIDEO_TRACK)
410     case CachedResource::TextTrackResource:
411 #endif
412         resourceType = DiagnosticLoggingKeys::otherKey();
413         break;
414     }
415     frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceKey(), DiagnosticLoggingKeys::loadedKey(), resourceType, ShouldSample::Yes);
416 }
417
418 bool SubresourceLoader::checkResponseCrossOriginAccessControl(const ResourceResponse& response, String& errorDescription)
419 {
420     if (!m_resource->isCrossOrigin() || options().mode != FetchOptions::Mode::Cors)
421         return true;
422
423     ASSERT(m_origin);
424     return passesAccessControlCheck(response, options().allowCredentials, *m_origin, errorDescription);
425 }
426
427 bool SubresourceLoader::checkRedirectionCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest, String& errorMessage)
428 {
429     bool crossOriginFlag = m_resource->isCrossOrigin();
430     bool isNextRequestCrossOrigin = m_origin && !m_origin->canRequest(newRequest.url());
431
432     if (isNextRequestCrossOrigin)
433         m_resource->setCrossOrigin();
434
435     ASSERT(options().mode != FetchOptions::Mode::SameOrigin || !m_resource->isCrossOrigin());
436
437     if (options().mode != FetchOptions::Mode::Cors)
438         return true;
439
440     // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 8 & 9.
441     if (m_resource->isCrossOrigin() && !isValidCrossOriginRedirectionURL(newRequest.url())) {
442         errorMessage = ASCIILiteral("URL is either a non-HTTP URL or contains credentials.");
443         return false;
444     }
445
446     ASSERT(m_origin);
447     if (crossOriginFlag && !passesAccessControlCheck(redirectResponse, options().allowCredentials, *m_origin, errorMessage))
448         return false;
449
450     bool redirectingToNewOrigin = false;
451     if (m_resource->isCrossOrigin()) {
452         if (!crossOriginFlag && isNextRequestCrossOrigin)
453             redirectingToNewOrigin = true;
454         else
455             redirectingToNewOrigin = !SecurityOrigin::create(previousRequest.url())->canRequest(newRequest.url());
456     }
457
458     // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 10.
459     if (crossOriginFlag && redirectingToNewOrigin)
460         m_origin = SecurityOrigin::createUnique();
461
462     if (redirectingToNewOrigin) {
463         cleanRedirectedRequestForAccessControl(newRequest);
464         updateRequestForAccessControl(newRequest, *m_origin, options().allowCredentials);
465     }
466
467     return true;
468 }
469
470 void SubresourceLoader::didFinishLoading(double finishTime)
471 {
472     if (m_state != Initialized)
473         return;
474     ASSERT(!reachedTerminalState());
475     ASSERT(!m_resource->resourceToRevalidate());
476     // FIXME (129394): We should cancel the load when a decode error occurs instead of continuing the load to completion.
477     ASSERT(!m_resource->errorOccurred() || m_resource->status() == CachedResource::DecodeError);
478     LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data());
479     logResourceLoaded(m_frame.get(), m_resource->type());
480
481     Ref<SubresourceLoader> protectedThis(*this);
482     CachedResourceHandle<CachedResource> protectResource(m_resource);
483
484     // FIXME: The finishTime that is passed in is from the NetworkProcess and is more accurate.
485     // However, all other load times are generated from the web process or offsets.
486     // Mixing times from different processes can cause the finish time to be earlier than
487     // the response received time due to inter-process communication lag.
488     UNUSED_PARAM(finishTime);
489     double responseEndTime = monotonicallyIncreasingTime();
490     m_loadTiming.setResponseEnd(responseEndTime);
491
492 #if ENABLE(WEB_TIMING)
493     if (m_documentLoader->cachedResourceLoader().document() && RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
494         m_documentLoader->cachedResourceLoader().resourceTimingInformation().addResourceTiming(m_resource, *m_documentLoader->cachedResourceLoader().document(), m_resource->loader()->loadTiming());
495 #endif
496
497     m_state = Finishing;
498     m_resource->setLoadFinishTime(responseEndTime); // FIXME: Users of the loadFinishTime should use the LoadTiming struct instead.
499     m_resource->finishLoading(resourceData());
500
501     if (wasCancelled())
502         return;
503     m_resource->finish();
504     ASSERT(!reachedTerminalState());
505     didFinishLoadingOnePart(responseEndTime);
506     notifyDone();
507     if (reachedTerminalState())
508         return;
509     releaseResources();
510 }
511
512 void SubresourceLoader::didFail(const ResourceError& error)
513 {
514     if (m_state != Initialized)
515         return;
516     ASSERT(!reachedTerminalState());
517     LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
518
519     Ref<SubresourceLoader> protectedThis(*this);
520     CachedResourceHandle<CachedResource> protectResource(m_resource);
521     m_state = Finishing;
522     if (m_resource->resourceToRevalidate())
523         MemoryCache::singleton().revalidationFailed(*m_resource);
524     m_resource->setResourceError(error);
525     if (!m_resource->isPreloaded())
526         MemoryCache::singleton().remove(*m_resource);
527     m_resource->error(CachedResource::LoadError);
528     cleanupForError(error);
529     notifyDone();
530     if (reachedTerminalState())
531         return;
532     releaseResources();
533 }
534
535 void SubresourceLoader::willCancel(const ResourceError& error)
536 {
537 #if PLATFORM(IOS)
538     // Since we defer initialization to scheduling time on iOS but
539     // CachedResourceLoader stores resources in the memory cache immediately,
540     // m_resource might be cached despite its loader not being initialized.
541     if (m_state != Initialized && m_state != Uninitialized)
542 #else
543     if (m_state != Initialized)
544 #endif
545         return;
546     ASSERT(!reachedTerminalState());
547     LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data());
548
549     Ref<SubresourceLoader> protectedThis(*this);
550 #if PLATFORM(IOS)
551     m_state = m_state == Uninitialized ? CancelledWhileInitializing : Finishing;
552 #else
553     m_state = Finishing;
554 #endif
555     auto& memoryCache = MemoryCache::singleton();
556     if (m_resource->resourceToRevalidate())
557         memoryCache.revalidationFailed(*m_resource);
558     m_resource->setResourceError(error);
559     memoryCache.remove(*m_resource);
560 }
561
562 void SubresourceLoader::didCancel(const ResourceError&)
563 {
564     if (m_state == Uninitialized)
565         return;
566
567     m_resource->cancelLoad();
568     notifyDone();
569 }
570
571 void SubresourceLoader::notifyDone()
572 {
573     if (reachedTerminalState())
574         return;
575
576     m_requestCountTracker = Nullopt;
577 #if PLATFORM(IOS)
578     m_documentLoader->cachedResourceLoader().loadDone(m_resource, m_state != CancelledWhileInitializing);
579 #else
580     m_documentLoader->cachedResourceLoader().loadDone(m_resource);
581 #endif
582     if (reachedTerminalState())
583         return;
584     m_documentLoader->removeSubresourceLoader(this);
585 }
586
587 void SubresourceLoader::releaseResources()
588 {
589     ASSERT(!reachedTerminalState());
590 #if PLATFORM(IOS)
591     if (m_state != Uninitialized && m_state != CancelledWhileInitializing)
592 #else
593     if (m_state != Uninitialized)
594 #endif
595         m_resource->clearLoader();
596     m_resource = nullptr;
597     ResourceLoader::releaseResources();
598 }
599
600 }