2 * Copyright (C) 2006, 2007, 2010, 2011 Apple Inc. All rights reserved.
3 * (C) 2007 Graham Dennis (graham.dennis@gmail.com)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "core/fetch/ResourceLoader.h"
33 #include "core/fetch/CSSStyleSheetResource.h"
34 #include "core/fetch/Resource.h"
35 #include "core/fetch/ResourceFetcher.h"
36 #include "core/fetch/ResourcePtr.h"
37 #include "platform/Logging.h"
38 #include "platform/SharedBuffer.h"
39 #include "platform/ThreadedDataReceiver.h"
40 #include "platform/exported/WrappedResourceRequest.h"
41 #include "platform/exported/WrappedResourceResponse.h"
42 #include "platform/network/ResourceError.h"
43 #include "public/platform/Platform.h"
44 #include "public/platform/WebData.h"
45 #include "public/platform/WebThreadedDataReceiver.h"
46 #include "public/platform/WebURLError.h"
47 #include "public/platform/WebURLRequest.h"
48 #include "public/platform/WebURLResponse.h"
49 #include "wtf/Assertions.h"
50 #include "wtf/CurrentTime.h"
54 ResourceLoader
* ResourceLoader::create(ResourceFetcher
* fetcher
, Resource
* resource
, const ResourceRequest
& request
, const ResourceLoaderOptions
& options
)
56 ResourceLoader
* loader
= new ResourceLoader(fetcher
, resource
, options
);
57 loader
->init(request
);
61 ResourceLoader::ResourceLoader(ResourceFetcher
* fetcher
, Resource
* resource
, const ResourceLoaderOptions
& options
)
63 , m_notifiedLoadComplete(false)
64 , m_defersLoading(fetcher
->defersLoading())
65 , m_loadingMultipartContent(false)
67 , m_resource(resource
)
68 , m_state(Initialized
)
69 , m_connectionState(ConnectionStateNew
)
73 ResourceLoader::~ResourceLoader()
75 ASSERT(m_state
== Terminated
);
78 DEFINE_TRACE(ResourceLoader
)
80 visitor
->trace(m_fetcher
);
81 visitor
->trace(m_resource
);
84 void ResourceLoader::releaseResources()
86 ASSERT(m_state
!= Terminated
);
87 ASSERT(m_notifiedLoadComplete
);
88 m_fetcher
->didLoadResource();
89 if (m_state
== Terminated
)
91 m_resource
->clearLoader();
92 m_resource
->deleteIfPossible();
95 ASSERT(m_state
!= Terminated
);
102 m_deferredRequest
= ResourceRequest();
106 void ResourceLoader::init(const ResourceRequest
& passedRequest
)
108 ASSERT(m_state
!= Terminated
);
109 ResourceRequest
request(passedRequest
);
110 m_fetcher
->willSendRequest(m_resource
->identifier(), request
, ResourceResponse(), m_options
.initiatorInfo
);
111 ASSERT(m_state
!= Terminated
);
112 ASSERT(!request
.isNull());
113 m_originalRequest
= m_request
= applyOptions(request
);
114 m_resource
->updateRequest(request
);
115 ASSERT(m_state
!= Terminated
);
116 m_fetcher
->didInitializeResourceLoader(this);
119 void ResourceLoader::start()
122 ASSERT(!m_request
.isNull());
123 ASSERT(m_deferredRequest
.isNull());
125 m_fetcher
->willStartLoadingResource(m_resource
, m_request
);
127 if (m_options
.synchronousPolicy
== RequestSynchronously
) {
128 requestSynchronously();
132 if (m_defersLoading
) {
133 m_deferredRequest
= m_request
;
137 if (m_state
== Terminated
)
140 RELEASE_ASSERT(m_connectionState
== ConnectionStateNew
);
141 m_connectionState
= ConnectionStateStarted
;
143 m_loader
= adoptPtr(Platform::current()->createURLLoader());
145 WrappedResourceRequest
wrappedRequest(m_request
);
146 m_loader
->loadAsynchronously(wrappedRequest
, this);
149 void ResourceLoader::changeToSynchronous()
151 ASSERT(m_options
.synchronousPolicy
== RequestAsynchronously
);
155 m_request
.setPriority(ResourceLoadPriorityHighest
);
156 m_connectionState
= ConnectionStateNew
;
157 requestSynchronously();
160 void ResourceLoader::setDefersLoading(bool defers
)
162 m_defersLoading
= defers
;
164 m_loader
->setDefersLoading(defers
);
165 if (!defers
&& !m_deferredRequest
.isNull()) {
166 m_request
= applyOptions(m_deferredRequest
);
167 m_deferredRequest
= ResourceRequest();
172 void ResourceLoader::attachThreadedDataReceiver(PassRefPtrWillBeRawPtr
<ThreadedDataReceiver
> threadedDataReceiver
)
175 // The implementor of the WebURLLoader assumes ownership of the
176 // threaded data receiver if it signals that it got successfully
178 WebThreadedDataReceiver
* webDataReceiver
= new WebThreadedDataReceiver(threadedDataReceiver
);
179 if (!m_loader
->attachThreadedDataReceiver(webDataReceiver
))
180 delete webDataReceiver
;
184 void ResourceLoader::didDownloadData(WebURLLoader
*, int length
, int encodedDataLength
)
186 ASSERT(m_state
!= Terminated
);
187 RELEASE_ASSERT(m_connectionState
== ConnectionStateReceivedResponse
);
188 m_fetcher
->didDownloadData(m_resource
, length
, encodedDataLength
);
189 if (m_state
== Terminated
)
191 m_resource
->didDownloadData(length
);
194 void ResourceLoader::didFinishLoadingOnePart(double finishTime
, int64_t encodedDataLength
)
196 // If load has been cancelled after finishing (which could happen with a
197 // JavaScript that changes the window location), do nothing.
198 if (m_state
== Terminated
)
201 if (m_notifiedLoadComplete
)
203 m_notifiedLoadComplete
= true;
204 m_fetcher
->didFinishLoading(m_resource
, finishTime
, encodedDataLength
);
207 void ResourceLoader::didChangePriority(ResourceLoadPriority loadPriority
, int intraPriorityValue
)
210 m_fetcher
->didChangeLoadingPriority(m_resource
, loadPriority
, intraPriorityValue
);
211 ASSERT(m_state
!= Terminated
);
212 m_loader
->didChangePriority(static_cast<WebURLRequest::Priority
>(loadPriority
), intraPriorityValue
);
216 bool ResourceLoader::shouldUseIncreasedPriorities()
218 return m_fetcher
->context().fetchIncreasePriorities();
221 void ResourceLoader::cancelIfNotFinishing()
223 if (m_state
!= Initialized
)
228 void ResourceLoader::cancel()
230 cancel(ResourceError());
233 void ResourceLoader::cancel(const ResourceError
& error
)
235 // If the load has already completed - succeeded, failed, or previously cancelled - do nothing.
236 if (m_state
== Terminated
)
238 if (m_state
== Finishing
) {
243 ResourceError nonNullError
= error
.isNull() ? ResourceError::cancelledError(m_request
.url()) : error
;
245 WTF_LOG(ResourceLoading
, "Cancelled load of '%s'.\n", m_resource
->url().string().latin1().data());
246 if (m_state
== Initialized
)
248 m_resource
->setResourceError(nonNullError
);
251 m_connectionState
= ConnectionStateCanceled
;
256 if (!m_notifiedLoadComplete
) {
257 m_notifiedLoadComplete
= true;
258 m_fetcher
->didFailLoading(m_resource
, nonNullError
);
261 if (m_state
== Finishing
)
262 m_resource
->error(Resource::LoadError
);
263 if (m_state
!= Terminated
)
267 void ResourceLoader::willSendRequest(WebURLLoader
*, WebURLRequest
& passedNewRequest
, const WebURLResponse
& passedRedirectResponse
)
269 ASSERT(m_state
!= Terminated
);
271 ResourceRequest
& newRequest(applyOptions(passedNewRequest
.toMutableResourceRequest()));
273 ASSERT(!newRequest
.isNull());
274 const ResourceResponse
& redirectResponse(passedRedirectResponse
.toResourceResponse());
275 ASSERT(!redirectResponse
.isNull());
276 newRequest
.setFollowedRedirect(true);
277 if (!m_fetcher
->canAccessRedirect(m_resource
, newRequest
, redirectResponse
, m_options
)) {
278 cancel(ResourceError::cancelledDueToAccessCheckError(newRequest
.url()));
281 ASSERT(m_state
!= Terminated
);
283 applyOptions(newRequest
); // canAccessRedirect() can modify m_options so we should re-apply it.
284 m_fetcher
->redirectReceived(m_resource
, redirectResponse
);
285 ASSERT(m_state
!= Terminated
);
286 m_resource
->willFollowRedirect(newRequest
, redirectResponse
);
287 if (newRequest
.isNull() || m_state
== Terminated
)
290 m_fetcher
->willSendRequest(m_resource
->identifier(), newRequest
, redirectResponse
, m_options
.initiatorInfo
);
291 ASSERT(m_state
!= Terminated
);
292 ASSERT(!newRequest
.isNull());
293 m_resource
->updateRequest(newRequest
);
294 m_request
= newRequest
;
297 void ResourceLoader::didReceiveCachedMetadata(WebURLLoader
*, const char* data
, int length
)
299 RELEASE_ASSERT(m_connectionState
== ConnectionStateReceivedResponse
|| m_connectionState
== ConnectionStateReceivingData
);
300 ASSERT(m_state
== Initialized
);
301 m_resource
->setSerializedCachedMetadata(data
, length
);
304 void ResourceLoader::didSendData(WebURLLoader
*, unsigned long long bytesSent
, unsigned long long totalBytesToBeSent
)
306 ASSERT(m_state
== Initialized
);
307 m_resource
->didSendData(bytesSent
, totalBytesToBeSent
);
310 bool ResourceLoader::responseNeedsAccessControlCheck() const
312 // If the fetch was (potentially) CORS enabled, an access control check of the response is required.
313 return m_options
.corsEnabled
== IsCORSEnabled
;
316 void ResourceLoader::didReceiveResponse(WebURLLoader
*, const WebURLResponse
& response
, WebDataConsumerHandle
* rawHandle
)
318 ASSERT(!response
.isNull());
319 ASSERT(m_state
== Initialized
);
320 // |rawHandle|'s ownership is transferred to the callee.
321 OwnPtr
<WebDataConsumerHandle
> handle
= adoptPtr(rawHandle
);
323 bool isMultipartPayload
= response
.isMultipartPayload();
324 bool isValidStateTransition
= (m_connectionState
== ConnectionStateStarted
|| m_connectionState
== ConnectionStateReceivedResponse
);
325 // In the case of multipart loads, calls to didReceiveData & didReceiveResponse can be interleaved.
326 RELEASE_ASSERT(isMultipartPayload
|| isValidStateTransition
);
327 m_connectionState
= ConnectionStateReceivedResponse
;
329 const ResourceResponse
& resourceResponse
= response
.toResourceResponse();
331 if (responseNeedsAccessControlCheck()) {
332 if (response
.wasFetchedViaServiceWorker()) {
333 if (response
.wasFallbackRequiredByServiceWorker()) {
336 m_connectionState
= ConnectionStateStarted
;
337 m_loader
= adoptPtr(Platform::current()->createURLLoader());
339 ASSERT(!m_request
.skipServiceWorker());
340 m_request
.setSkipServiceWorker(true);
341 WrappedResourceRequest
wrappedRequest(m_request
);
342 m_loader
->loadAsynchronously(wrappedRequest
, this);
346 // If the response successfully validated a cached resource, perform
347 // the access control with respect to it. Need to do this right here
348 // before the resource switches clients over to that validated resource.
349 Resource
* resource
= m_resource
;
350 if (resource
->isCacheValidator() && resourceResponse
.httpStatusCode() == 304)
351 resource
= m_resource
->resourceToRevalidate();
353 m_resource
->setResponse(resourceResponse
);
354 if (!m_fetcher
->canAccessResource(resource
, m_options
.securityOrigin
.get(), response
.url(), ResourceFetcher::ShouldLogAccessControlErrors
)) {
355 m_fetcher
->didReceiveResponse(m_resource
, resourceResponse
);
356 cancel(ResourceError::cancelledDueToAccessCheckError(KURL(response
.url())));
362 m_resource
->responseReceived(resourceResponse
, handle
.release());
363 if (m_state
== Terminated
)
366 m_fetcher
->didReceiveResponse(m_resource
, resourceResponse
);
367 if (m_state
== Terminated
)
370 if (response
.toResourceResponse().isMultipart()) {
371 // We only support multipart for images, though the image may be loaded
372 // as a main resource that we end up displaying through an ImageDocument.
373 if (!m_resource
->isImage() && m_resource
->type() != Resource::MainResource
) {
377 m_loadingMultipartContent
= true;
378 } else if (isMultipartPayload
) {
379 // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
380 // After the first multipart section is complete, signal to delegates that this load is "finished"
381 m_fetcher
->subresourceLoaderFinishedLoadingOnePart(this);
382 didFinishLoadingOnePart(0, WebURLLoaderClient::kUnknownEncodedDataLength
);
384 if (m_state
== Terminated
)
387 if (m_resource
->response().httpStatusCode() < 400 || m_resource
->shouldIgnoreHTTPStatusCodeErrors())
391 if (!m_notifiedLoadComplete
) {
392 m_notifiedLoadComplete
= true;
393 m_fetcher
->didFailLoading(m_resource
, ResourceError::cancelledError(m_request
.url()));
396 ASSERT(m_state
!= Terminated
);
397 m_resource
->error(Resource::LoadError
);
401 void ResourceLoader::didReceiveResponse(WebURLLoader
* loader
, const WebURLResponse
& response
)
403 didReceiveResponse(loader
, response
, nullptr);
406 void ResourceLoader::didReceiveData(WebURLLoader
*, const char* data
, int length
, int encodedDataLength
)
408 ASSERT(m_state
!= Terminated
);
409 RELEASE_ASSERT(m_connectionState
== ConnectionStateReceivedResponse
|| m_connectionState
== ConnectionStateReceivingData
);
410 m_connectionState
= ConnectionStateReceivingData
;
412 // It is possible to receive data on uninitialized resources if it had an error status code, and we are running a nested message
413 // loop. When this occurs, ignoring the data is the correct action.
414 if (m_resource
->response().httpStatusCode() >= 400 && !m_resource
->shouldIgnoreHTTPStatusCodeErrors())
416 ASSERT(m_state
== Initialized
);
418 // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
419 // However, with today's computers and networking speeds, this won't happen in practice.
420 // Could be an issue with a giant local file.
421 m_fetcher
->didReceiveData(m_resource
, data
, length
, encodedDataLength
);
422 if (m_state
== Terminated
)
424 RELEASE_ASSERT(length
>= 0);
425 m_resource
->appendData(data
, length
);
428 void ResourceLoader::didFinishLoading(WebURLLoader
*, double finishTime
, int64_t encodedDataLength
)
430 RELEASE_ASSERT(m_connectionState
== ConnectionStateReceivedResponse
|| m_connectionState
== ConnectionStateReceivingData
);
431 m_connectionState
= ConnectionStateFinishedLoading
;
432 if (m_state
!= Initialized
)
434 ASSERT(m_state
!= Terminated
);
435 WTF_LOG(ResourceLoading
, "Received '%s'.", m_resource
->url().string().latin1().data());
437 ResourcePtr
<Resource
> protectResource(m_resource
);
439 m_resource
->setLoadFinishTime(finishTime
);
440 didFinishLoadingOnePart(finishTime
, encodedDataLength
);
441 if (m_state
== Terminated
)
443 m_resource
->finish();
445 // If the load has been cancelled by a delegate in response to didFinishLoad(), do not release
446 // the resources a second time, they have been released by cancel.
447 if (m_state
== Terminated
)
452 void ResourceLoader::didFail(WebURLLoader
*, const WebURLError
& error
)
454 m_connectionState
= ConnectionStateFailed
;
455 ASSERT(m_state
!= Terminated
);
456 WTF_LOG(ResourceLoading
, "Failed to load '%s'.\n", m_resource
->url().string().latin1().data());
458 ResourcePtr
<Resource
> protectResource(m_resource
);
460 m_resource
->setResourceError(error
);
462 if (!m_notifiedLoadComplete
) {
463 m_notifiedLoadComplete
= true;
464 m_fetcher
->didFailLoading(m_resource
, error
);
466 if (m_state
== Terminated
)
469 m_resource
->error(Resource::LoadError
);
471 if (m_state
== Terminated
)
477 bool ResourceLoader::isLoadedBy(ResourceFetcher
* loader
) const
479 return m_fetcher
->isLoadedBy(loader
);
482 void ResourceLoader::requestSynchronously()
484 OwnPtr
<WebURLLoader
> loader
= adoptPtr(Platform::current()->createURLLoader());
487 // downloadToFile is not supported for synchronous requests.
488 ASSERT(!m_request
.downloadToFile());
490 ResourcePtr
<Resource
> protectResource(m_resource
);
492 RELEASE_ASSERT(m_connectionState
== ConnectionStateNew
);
493 m_connectionState
= ConnectionStateStarted
;
495 WrappedResourceRequest
requestIn(m_request
);
496 WebURLResponse responseOut
;
497 responseOut
.initialize();
498 WebURLError errorOut
;
500 loader
->loadSynchronously(requestIn
, responseOut
, errorOut
, dataOut
);
501 if (errorOut
.reason
) {
502 if (m_state
== Terminated
) {
503 // A message dispatched while synchronously fetching the resource
504 // can bring about the cancellation of this load.
508 didFail(0, errorOut
);
511 didReceiveResponse(0, responseOut
);
512 if (m_state
== Terminated
)
514 RefPtr
<ResourceLoadInfo
> resourceLoadInfo
= responseOut
.toResourceResponse().resourceLoadInfo();
515 int64_t encodedDataLength
= resourceLoadInfo
? resourceLoadInfo
->encodedDataLength
: WebURLLoaderClient::kUnknownEncodedDataLength
;
516 m_fetcher
->didReceiveData(m_resource
, dataOut
.data(), dataOut
.size(), encodedDataLength
);
517 m_resource
->setResourceBuffer(dataOut
);
518 didFinishLoading(0, monotonicallyIncreasingTime(), encodedDataLength
);
521 ResourceRequest
& ResourceLoader::applyOptions(ResourceRequest
& request
) const
523 request
.setAllowStoredCredentials(m_options
.allowCredentials
== AllowStoredCredentials
);