1/* 2 * Copyright (C) 2006, 2007, 2009 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 "Document.h" 34#include "DocumentLoader.h" 35#include "Frame.h" 36#include "FrameLoader.h" 37#include "Logging.h" 38#include "MemoryCache.h" 39#include "Page.h" 40#include "PageActivityAssertionToken.h" 41#include "ResourceBuffer.h" 42#include <wtf/Ref.h> 43#include <wtf/RefCountedLeakCounter.h> 44#include <wtf/StdLibExtras.h> 45#include <wtf/text/CString.h> 46 47#if PLATFORM(IOS) 48#include <RuntimeApplicationChecksIOS.h> 49#endif 50 51namespace WebCore { 52 53DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, subresourceLoaderCounter, ("SubresourceLoader")); 54 55SubresourceLoader::RequestCountTracker::RequestCountTracker(CachedResourceLoader* cachedResourceLoader, CachedResource* resource) 56 : m_cachedResourceLoader(cachedResourceLoader) 57 , m_resource(resource) 58{ 59 m_cachedResourceLoader->incrementRequestCount(m_resource); 60} 61 62SubresourceLoader::RequestCountTracker::~RequestCountTracker() 63{ 64 m_cachedResourceLoader->decrementRequestCount(m_resource); 65} 66 67SubresourceLoader::SubresourceLoader(Frame* frame, CachedResource* resource, const ResourceLoaderOptions& options) 68 : ResourceLoader(frame, options) 69 , m_resource(resource) 70 , m_loadingMultipartContent(false) 71 , m_state(Uninitialized) 72 , m_requestCountTracker(adoptPtr(new RequestCountTracker(frame->document()->cachedResourceLoader(), resource))) 73{ 74#ifndef NDEBUG 75 subresourceLoaderCounter.increment(); 76#endif 77} 78 79SubresourceLoader::~SubresourceLoader() 80{ 81 ASSERT(m_state != Initialized); 82 ASSERT(reachedTerminalState()); 83#ifndef NDEBUG 84 subresourceLoaderCounter.decrement(); 85#endif 86} 87 88PassRefPtr<SubresourceLoader> SubresourceLoader::create(Frame* frame, CachedResource* resource, const ResourceRequest& request, const ResourceLoaderOptions& options) 89{ 90 RefPtr<SubresourceLoader> subloader(adoptRef(new SubresourceLoader(frame, resource, options))); 91#if PLATFORM(IOS) 92 if (!applicationIsWebProcess()) { 93 // On iOS, do not invoke synchronous resource load delegates while resource load scheduling 94 // is disabled to avoid re-entering style selection from a different thread (see <rdar://problem/9121719>). 95 // FIXME: This should be fixed for all ports in <https://bugs.webkit.org/show_bug.cgi?id=56647>. 96 subloader->m_iOSOriginalRequest = request; 97 return subloader.release(); 98 } 99#endif 100 if (!subloader->init(request)) 101 return nullptr; 102 return subloader.release(); 103} 104 105#if PLATFORM(IOS) 106bool SubresourceLoader::startLoading() 107{ 108 ASSERT(!applicationIsWebProcess()); 109 if (!init(m_iOSOriginalRequest)) 110 return false; 111 m_iOSOriginalRequest = ResourceRequest(); 112 start(); 113 return true; 114} 115#endif 116 117CachedResource* SubresourceLoader::cachedResource() 118{ 119 return m_resource; 120} 121 122void SubresourceLoader::cancelIfNotFinishing() 123{ 124 if (m_state != Initialized) 125 return; 126 127 ResourceLoader::cancel(); 128} 129 130bool SubresourceLoader::init(const ResourceRequest& request) 131{ 132 if (!ResourceLoader::init(request)) 133 return false; 134 135 ASSERT(!reachedTerminalState()); 136 m_state = Initialized; 137 m_documentLoader->addSubresourceLoader(this); 138 return true; 139} 140 141bool SubresourceLoader::isSubresourceLoader() 142{ 143 return true; 144} 145 146void SubresourceLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse) 147{ 148 // Store the previous URL because the call to ResourceLoader::willSendRequest will modify it. 149 URL previousURL = request().url(); 150 Ref<SubresourceLoader> protect(*this); 151 152 ASSERT(!newRequest.isNull()); 153 if (!redirectResponse.isNull()) { 154 // CachedResources are keyed off their original request URL. 155 // Requesting the same original URL a second time can redirect to a unique second resource. 156 // Therefore, if a redirect to a different destination URL occurs, we should no longer consider this a revalidation of the first resource. 157 // Doing so would have us reusing the resource from the first request if the second request's revalidation succeeds. 158 if (newRequest.isConditional() && m_resource->resourceToRevalidate() && newRequest.url() != m_resource->resourceToRevalidate()->response().url()) { 159 newRequest.makeUnconditional(); 160 memoryCache()->revalidationFailed(m_resource); 161 } 162 163 if (!m_documentLoader->cachedResourceLoader().canRequest(m_resource->type(), newRequest.url(), options())) { 164 cancel(); 165 return; 166 } 167 if (m_resource->isImage() && m_documentLoader->cachedResourceLoader().shouldDeferImageLoad(newRequest.url())) { 168 cancel(); 169 return; 170 } 171 m_resource->willSendRequest(newRequest, redirectResponse); 172 } 173 174 if (newRequest.isNull() || reachedTerminalState()) 175 return; 176 177 ResourceLoader::willSendRequest(newRequest, redirectResponse); 178 if (newRequest.isNull()) 179 cancel(); 180} 181 182void SubresourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) 183{ 184 ASSERT(m_state == Initialized); 185 Ref<SubresourceLoader> protect(*this); 186 m_resource->didSendData(bytesSent, totalBytesToBeSent); 187} 188 189void SubresourceLoader::didReceiveResponse(const ResourceResponse& response) 190{ 191 ASSERT(!response.isNull()); 192 ASSERT(m_state == Initialized); 193 194 // Reference the object in this method since the additional processing can do 195 // anything including removing the last reference to this object; one example of this is 3266216. 196 Ref<SubresourceLoader> protect(*this); 197 198 if (m_resource->resourceToRevalidate()) { 199 if (response.httpStatusCode() == 304) { 200 // 304 Not modified / Use local copy 201 // Existing resource is ok, just use it updating the expiration time. 202 m_resource->setResponse(response); 203 memoryCache()->revalidationSucceeded(m_resource, response); 204 if (!reachedTerminalState()) 205 ResourceLoader::didReceiveResponse(response); 206 return; 207 } 208 // Did not get 304 response, continue as a regular resource load. 209 memoryCache()->revalidationFailed(m_resource); 210 } 211 212 m_resource->responseReceived(response); 213 if (reachedTerminalState()) 214 return; 215 216 ResourceLoader::didReceiveResponse(response); 217 if (reachedTerminalState()) 218 return; 219 220 // FIXME: Main resources have a different set of rules for multipart than images do. 221 // Hopefully we can merge those 2 paths. 222 if (response.isMultipart() && m_resource->type() != CachedResource::MainResource) { 223 m_loadingMultipartContent = true; 224 225 // We don't count multiParts in a CachedResourceLoader's request count 226 m_requestCountTracker.clear(); 227 if (!m_resource->isImage()) { 228 cancel(); 229 return; 230 } 231 } 232 233 RefPtr<ResourceBuffer> buffer = resourceData(); 234 if (m_loadingMultipartContent && buffer && buffer->size()) { 235 // The resource data will change as the next part is loaded, so we need to make a copy. 236 RefPtr<ResourceBuffer> copiedData = ResourceBuffer::create(buffer->data(), buffer->size()); 237 m_resource->finishLoading(copiedData.get()); 238 clearResourceData(); 239 // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once. 240 // After the first multipart section is complete, signal to delegates that this load is "finished" 241 m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this); 242 didFinishLoadingOnePart(0); 243 } 244 245 checkForHTTPStatusCodeError(); 246} 247 248void SubresourceLoader::didReceiveData(const char* data, unsigned length, long long encodedDataLength, DataPayloadType dataPayloadType) 249{ 250 didReceiveDataOrBuffer(data, length, 0, encodedDataLength, dataPayloadType); 251} 252 253void SubresourceLoader::didReceiveBuffer(PassRefPtr<SharedBuffer> buffer, long long encodedDataLength, DataPayloadType dataPayloadType) 254{ 255 didReceiveDataOrBuffer(0, 0, buffer, encodedDataLength, dataPayloadType); 256} 257 258void SubresourceLoader::didReceiveDataOrBuffer(const char* data, int length, PassRefPtr<SharedBuffer> prpBuffer, long long encodedDataLength, DataPayloadType dataPayloadType) 259{ 260 if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors()) 261 return; 262 ASSERT(!m_resource->resourceToRevalidate()); 263 ASSERT(!m_resource->errorOccurred()); 264 ASSERT(m_state == Initialized); 265 // Reference the object in this method since the additional processing can do 266 // anything including removing the last reference to this object; one example of this is 3266216. 267 Ref<SubresourceLoader> protect(*this); 268 RefPtr<SharedBuffer> buffer = prpBuffer; 269 270 ResourceLoader::didReceiveDataOrBuffer(data, length, buffer, encodedDataLength, dataPayloadType); 271 272 if (!m_loadingMultipartContent) { 273 if (ResourceBuffer* resourceData = this->resourceData()) 274 m_resource->addDataBuffer(resourceData); 275 else 276 m_resource->addData(buffer ? buffer->data() : data, buffer ? buffer->size() : length); 277 } 278} 279 280bool SubresourceLoader::checkForHTTPStatusCodeError() 281{ 282 if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors()) 283 return false; 284 285 m_state = Finishing; 286 m_resource->error(CachedResource::LoadError); 287 cancel(); 288 return true; 289} 290 291void SubresourceLoader::didFinishLoading(double finishTime) 292{ 293 if (m_state != Initialized) 294 return; 295 ASSERT(!reachedTerminalState()); 296 ASSERT(!m_resource->resourceToRevalidate()); 297 // FIXME (129394): We should cancel the load when a decode error occurs instead of continuing the load to completion. 298 ASSERT(!m_resource->errorOccurred() || m_resource->status() == CachedResource::DecodeError); 299 LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data()); 300 301 Ref<SubresourceLoader> protect(*this); 302 303#if PLATFORM(IOS) 304 if (resourceData()) 305 resourceData()->setShouldUsePurgeableMemory(true); 306#endif 307 CachedResourceHandle<CachedResource> protectResource(m_resource); 308 m_state = Finishing; 309 m_resource->setLoadFinishTime(finishTime); 310 m_resource->finishLoading(resourceData()); 311 312 if (wasCancelled()) 313 return; 314 m_resource->finish(); 315 ASSERT(!reachedTerminalState()); 316 didFinishLoadingOnePart(finishTime); 317 notifyDone(); 318 if (reachedTerminalState()) 319 return; 320 releaseResources(); 321} 322 323void SubresourceLoader::didFail(const ResourceError& error) 324{ 325 if (m_state != Initialized) 326 return; 327 ASSERT(!reachedTerminalState()); 328 LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data()); 329 330 Ref<SubresourceLoader> protect(*this); 331 CachedResourceHandle<CachedResource> protectResource(m_resource); 332 m_state = Finishing; 333 if (m_resource->resourceToRevalidate()) 334 memoryCache()->revalidationFailed(m_resource); 335 m_resource->setResourceError(error); 336 if (!m_resource->isPreloaded()) 337 memoryCache()->remove(m_resource); 338 m_resource->error(CachedResource::LoadError); 339 cleanupForError(error); 340 notifyDone(); 341 if (reachedTerminalState()) 342 return; 343 releaseResources(); 344} 345 346void SubresourceLoader::willCancel(const ResourceError& error) 347{ 348#if PLATFORM(IOS) 349 // Since we defer initialization to scheduling time on iOS but 350 // CachedResourceLoader stores resources in the memory cache immediately, 351 // m_resource might be cached despite its loader not being initialized. 352 if (m_state != Initialized && m_state != Uninitialized) 353#else 354 if (m_state != Initialized) 355#endif 356 return; 357 ASSERT(!reachedTerminalState()); 358 LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data()); 359 360 Ref<SubresourceLoader> protect(*this); 361#if PLATFORM(IOS) 362 m_state = m_state == Uninitialized ? CancelledWhileInitializing : Finishing; 363#else 364 m_state = Finishing; 365#endif 366 if (m_resource->resourceToRevalidate()) 367 memoryCache()->revalidationFailed(m_resource); 368 m_resource->setResourceError(error); 369 memoryCache()->remove(m_resource); 370} 371 372void SubresourceLoader::didCancel(const ResourceError&) 373{ 374 if (m_state == Uninitialized) 375 return; 376 377 m_resource->cancelLoad(); 378 notifyDone(); 379} 380 381void SubresourceLoader::notifyDone() 382{ 383 if (reachedTerminalState()) 384 return; 385 386 m_requestCountTracker.clear(); 387#if PLATFORM(IOS) 388 m_documentLoader->cachedResourceLoader().loadDone(m_resource, m_state != CancelledWhileInitializing); 389#else 390 m_documentLoader->cachedResourceLoader().loadDone(m_resource); 391#endif 392 if (reachedTerminalState()) 393 return; 394 m_documentLoader->removeSubresourceLoader(this); 395} 396 397void SubresourceLoader::releaseResources() 398{ 399 ASSERT(!reachedTerminalState()); 400#if PLATFORM(IOS) 401 if (m_state != Uninitialized && m_state != CancelledWhileInitializing) 402#else 403 if (m_state != Uninitialized) 404#endif 405 m_resource->clearLoader(); 406 m_resource = 0; 407 ResourceLoader::releaseResources(); 408} 409 410} 411