/* Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) Copyright (C) 2001 Dirk Mueller (mueller@kde.org) Copyright (C) 2002 Waldo Bastian (bastian@kde.org) Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include "CachedResourceRequest.h" #include "MemoryCache.h" #include "CachedImage.h" #include "CachedResource.h" #include "CachedResourceLoader.h" #include "Frame.h" #include "FrameLoader.h" #include "Logging.h" #include "ResourceHandle.h" #include "ResourceLoadScheduler.h" #include "ResourceRequest.h" #include "ResourceResponse.h" #include "SharedBuffer.h" #include #include #include namespace WebCore { static ResourceRequest::TargetType cachedResourceTypeToTargetType(CachedResource::Type type) { switch (type) { case CachedResource::CSSStyleSheet: #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: #endif return ResourceRequest::TargetIsStyleSheet; case CachedResource::Script: return ResourceRequest::TargetIsScript; case CachedResource::FontResource: return ResourceRequest::TargetIsFontResource; case CachedResource::ImageResource: return ResourceRequest::TargetIsImage; #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: return ResourceRequest::TargetIsPrefetch; case CachedResource::LinkPrerender: return ResourceRequest::TargetIsSubresource; case CachedResource::LinkSubresource: return ResourceRequest::TargetIsSubresource; #endif } ASSERT_NOT_REACHED(); return ResourceRequest::TargetIsSubresource; } CachedResourceRequest::CachedResourceRequest(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental) : m_cachedResourceLoader(cachedResourceLoader) , m_resource(resource) , m_incremental(incremental) , m_multipart(false) , m_finishing(false) { m_resource->setRequest(this); } CachedResourceRequest::~CachedResourceRequest() { m_resource->setRequest(0); } PassRefPtr CachedResourceRequest::load(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) { RefPtr request = adoptRef(new CachedResourceRequest(cachedResourceLoader, resource, incremental)); ResourceRequest resourceRequest = resource->resourceRequest(); resourceRequest.setTargetType(cachedResourceTypeToTargetType(resource->type())); if (!resource->accept().isEmpty()) resourceRequest.setHTTPAccept(resource->accept()); if (resource->isCacheValidator()) { CachedResource* resourceToRevalidate = resource->resourceToRevalidate(); ASSERT(resourceToRevalidate->canUseCacheValidator()); ASSERT(resourceToRevalidate->isLoaded()); const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified"); const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag"); if (!lastModified.isEmpty() || !eTag.isEmpty()) { ASSERT(cachedResourceLoader->cachePolicy() != CachePolicyReload); if (cachedResourceLoader->cachePolicy() == CachePolicyRevalidate) resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0"); if (!lastModified.isEmpty()) resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified); if (!eTag.isEmpty()) resourceRequest.setHTTPHeaderField("If-None-Match", eTag); } } #if ENABLE(LINK_PREFETCH) if (resource->type() == CachedResource::LinkPrefetch || resource->type() == CachedResource::LinkPrerender || resource->type() == CachedResource::LinkSubresource) resourceRequest.setHTTPHeaderField("Purpose", "prefetch"); #endif ResourceLoadPriority priority = resource->loadPriority(); resourceRequest.setPriority(priority); RefPtr loader = resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->document()->frame(), request.get(), resourceRequest, priority, securityCheck, sendResourceLoadCallbacks); if (!loader || loader->reachedTerminalState()) { // FIXME: What if resources in other frames were waiting for this revalidation? LOG(ResourceLoading, "Cannot start loading '%s'", resource->url().string().latin1().data()); cachedResourceLoader->decrementRequestCount(resource); cachedResourceLoader->loadFinishing(); if (resource->resourceToRevalidate()) memoryCache()->revalidationFailed(resource); resource->error(CachedResource::LoadError); cachedResourceLoader->loadDone(0); return 0; } request->m_loader = loader; return request.release(); } void CachedResourceRequest::willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse&) { m_resource->setRequestedFromNetworkingLayer(); } void CachedResourceRequest::didFinishLoading(SubresourceLoader* loader, double) { if (m_finishing) return; ASSERT(loader == m_loader.get()); ASSERT(!m_resource->resourceToRevalidate()); LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data()); // Prevent the document from being destroyed before we are done with // the cachedResourceLoader that it will delete when the document gets deleted. RefPtr protector(m_cachedResourceLoader->document()); if (!m_multipart) m_cachedResourceLoader->decrementRequestCount(m_resource); m_finishing = true; // If we got a 4xx response, we're pretending to have received a network // error, so we can't send the successful data() and finish() callbacks. if (!m_resource->errorOccurred()) { m_cachedResourceLoader->loadFinishing(); m_resource->data(loader->resourceData(), true); if (!m_resource->errorOccurred()) m_resource->finish(); } m_cachedResourceLoader->loadDone(this); } void CachedResourceRequest::didFail(SubresourceLoader*, const ResourceError&) { if (!m_loader) return; didFail(); } void CachedResourceRequest::didFail(bool cancelled) { if (m_finishing) return; LOG(ResourceLoading, "Failed to load '%s' (cancelled=%d).\n", m_resource->url().string().latin1().data(), cancelled); // Prevent the document from being destroyed before we are done with // the cachedResourceLoader that it will delete when the document gets deleted. RefPtr protector(m_cachedResourceLoader->document()); if (!m_multipart) m_cachedResourceLoader->decrementRequestCount(m_resource); m_finishing = true; m_loader->clearClient(); if (m_resource->resourceToRevalidate()) memoryCache()->revalidationFailed(m_resource); if (!cancelled) { m_cachedResourceLoader->loadFinishing(); m_resource->error(CachedResource::LoadError); } if (cancelled || !m_resource->isPreloaded()) memoryCache()->remove(m_resource); m_cachedResourceLoader->loadDone(this); } void CachedResourceRequest::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response) { ASSERT(loader == m_loader.get()); if (m_resource->isCacheValidator()) { if (response.httpStatusCode() == 304) { // 304 Not modified / Use local copy loader->clearClient(); RefPtr protector(m_cachedResourceLoader->document()); m_cachedResourceLoader->decrementRequestCount(m_resource); m_finishing = true; // Existing resource is ok, just use it updating the expiration time. memoryCache()->revalidationSucceeded(m_resource, response); if (m_cachedResourceLoader->frame()) m_cachedResourceLoader->frame()->loader()->checkCompleted(); m_cachedResourceLoader->loadDone(this); return; } // Did not get 304 response, continue as a regular resource load. memoryCache()->revalidationFailed(m_resource); } m_resource->setResponse(response); String encoding = response.textEncodingName(); if (!encoding.isNull()) m_resource->setEncoding(encoding); if (m_multipart) { ASSERT(m_resource->isImage()); static_cast(m_resource)->clear(); if (m_cachedResourceLoader->frame()) m_cachedResourceLoader->frame()->loader()->checkCompleted(); } else if (response.isMultipart()) { m_multipart = true; // We don't count multiParts in a CachedResourceLoader's request count m_cachedResourceLoader->decrementRequestCount(m_resource); // If we get a multipart response, we must have a handle ASSERT(loader->handle()); if (!m_resource->isImage()) loader->handle()->cancel(); } } void CachedResourceRequest::didReceiveData(SubresourceLoader* loader, const char* data, int size) { ASSERT(loader == m_loader.get()); ASSERT(!m_resource->isCacheValidator()); if (m_resource->errorOccurred()) return; if (m_resource->response().httpStatusCode() >= 400) { if (!m_resource->shouldIgnoreHTTPStatusCodeErrors()) m_resource->error(CachedResource::LoadError); return; } // Set the data. if (m_multipart) { // The loader delivers the data in a multipart section all at once, send eof. // The resource data will change as the next part is loaded, so we need to make a copy. RefPtr copiedData = SharedBuffer::create(data, size); m_resource->data(copiedData.release(), true); } else if (m_incremental) m_resource->data(loader->resourceData(), false); } void CachedResourceRequest::didReceiveCachedMetadata(SubresourceLoader*, const char* data, int size) { ASSERT(!m_resource->isCacheValidator()); m_resource->setSerializedCachedMetadata(data, size); } } //namespace WebCore