| /* |
| 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 "CachedResource.h" |
| |
| #include "Cache.h" |
| #include "CachedResourceHandle.h" |
| #include "DocLoader.h" |
| #include "Frame.h" |
| #include "FrameLoader.h" |
| #include "KURL.h" |
| #include "PurgeableBuffer.h" |
| #include "Request.h" |
| #include <wtf/CurrentTime.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/RefCountedLeakCounter.h> |
| #include <wtf/StdLibExtras.h> |
| #include <wtf/Vector.h> |
| |
| using namespace WTF; |
| |
| namespace WebCore { |
| |
| #ifndef NDEBUG |
| static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource"); |
| #endif |
| |
| CachedResource::CachedResource(const String& url, Type type) |
| : m_url(url) |
| , m_responseTimestamp(currentTime()) |
| , m_lastDecodedAccessTime(0) |
| , m_sendResourceLoadCallbacks(true) |
| , m_preloadCount(0) |
| , m_preloadResult(PreloadNotReferenced) |
| , m_requestedFromNetworkingLayer(false) |
| , m_inCache(false) |
| , m_loading(false) |
| , m_docLoader(0) |
| , m_handleCount(0) |
| , m_resourceToRevalidate(0) |
| , m_isBeingRevalidated(false) |
| { |
| #ifndef NDEBUG |
| cachedResourceLeakCounter.increment(); |
| #endif |
| |
| m_type = type; |
| m_status = Pending; |
| m_encodedSize = 0; |
| m_decodedSize = 0; |
| m_request = 0; |
| |
| m_accessCount = 0; |
| m_inLiveDecodedResourcesList = false; |
| |
| m_nextInAllResourcesList = 0; |
| m_prevInAllResourcesList = 0; |
| |
| m_nextInLiveResourcesList = 0; |
| m_prevInLiveResourcesList = 0; |
| |
| #ifndef NDEBUG |
| m_deleted = false; |
| m_lruIndex = 0; |
| #endif |
| m_errorOccurred = false; |
| } |
| |
| CachedResource::~CachedResource() |
| { |
| ASSERT(!inCache()); |
| ASSERT(!m_deleted); |
| ASSERT(url().isNull() || cache()->resourceForURL(url()) != this); |
| #ifndef NDEBUG |
| m_deleted = true; |
| cachedResourceLeakCounter.decrement(); |
| #endif |
| |
| if (m_resourceToRevalidate) |
| m_resourceToRevalidate->m_isBeingRevalidated = false; |
| |
| if (m_docLoader) |
| m_docLoader->removeCachedResource(this); |
| } |
| |
| void CachedResource::load(DocLoader* docLoader, bool incremental, bool skipCanLoadCheck, bool sendResourceLoadCallbacks) |
| { |
| m_sendResourceLoadCallbacks = sendResourceLoadCallbacks; |
| cache()->loader()->load(docLoader, this, incremental, skipCanLoadCheck, sendResourceLoadCallbacks); |
| m_loading = true; |
| } |
| |
| void CachedResource::finish() |
| { |
| m_status = Cached; |
| } |
| |
| bool CachedResource::isExpired() const |
| { |
| if (m_response.isNull()) |
| return false; |
| |
| return currentAge() > freshnessLifetime(); |
| } |
| |
| double CachedResource::currentAge() const |
| { |
| // RFC2616 13.2.3 |
| // No compensation for latency as that is not terribly important in practice |
| double dateValue = m_response.date(); |
| double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0; |
| double ageValue = m_response.age(); |
| double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge; |
| double residentTime = currentTime() - m_responseTimestamp; |
| return correctedReceivedAge + residentTime; |
| } |
| |
| double CachedResource::freshnessLifetime() const |
| { |
| // Cache non-http resources liberally |
| if (!m_response.url().protocolInHTTPFamily()) |
| return std::numeric_limits<double>::max(); |
| |
| // RFC2616 13.2.4 |
| double maxAgeValue = m_response.cacheControlMaxAge(); |
| if (isfinite(maxAgeValue)) |
| return maxAgeValue; |
| double expiresValue = m_response.expires(); |
| double dateValue = m_response.date(); |
| double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp; |
| if (isfinite(expiresValue)) |
| return expiresValue - creationTime; |
| double lastModifiedValue = m_response.lastModified(); |
| if (isfinite(lastModifiedValue)) |
| return (creationTime - lastModifiedValue) * 0.1; |
| // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0. |
| return 0; |
| } |
| |
| void CachedResource::setResponse(const ResourceResponse& response) |
| { |
| m_response = response; |
| m_responseTimestamp = currentTime(); |
| } |
| |
| void CachedResource::setRequest(Request* request) |
| { |
| if (request && !m_request) |
| m_status = Pending; |
| m_request = request; |
| if (canDelete() && !inCache()) |
| delete this; |
| } |
| |
| void CachedResource::addClient(CachedResourceClient* client) |
| { |
| addClientToSet(client); |
| didAddClient(client); |
| } |
| |
| void CachedResource::addClientToSet(CachedResourceClient* client) |
| { |
| ASSERT(!isPurgeable()); |
| |
| if (m_preloadResult == PreloadNotReferenced) { |
| if (isLoaded()) |
| m_preloadResult = PreloadReferencedWhileComplete; |
| else if (m_requestedFromNetworkingLayer) |
| m_preloadResult = PreloadReferencedWhileLoading; |
| else |
| m_preloadResult = PreloadReferenced; |
| } |
| if (!hasClients() && inCache()) |
| cache()->addToLiveResourcesSize(this); |
| m_clients.add(client); |
| } |
| |
| void CachedResource::removeClient(CachedResourceClient* client) |
| { |
| ASSERT(m_clients.contains(client)); |
| m_clients.remove(client); |
| |
| if (canDelete() && !inCache()) |
| delete this; |
| else if (!hasClients() && inCache()) { |
| cache()->removeFromLiveResourcesSize(this); |
| cache()->removeFromLiveDecodedResourcesList(this); |
| allClientsRemoved(); |
| if (response().cacheControlContainsNoStore()) { |
| // RFC2616 14.9.2: |
| // "no-store: ...MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" |
| cache()->remove(this); |
| } else |
| cache()->prune(); |
| } |
| // This object may be dead here. |
| } |
| |
| void CachedResource::deleteIfPossible() |
| { |
| if (canDelete() && !inCache()) |
| delete this; |
| } |
| |
| void CachedResource::setDecodedSize(unsigned size) |
| { |
| if (size == m_decodedSize) |
| return; |
| |
| int delta = size - m_decodedSize; |
| |
| // The object must now be moved to a different queue, since its size has been changed. |
| // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous |
| // queue. |
| if (inCache()) |
| cache()->removeFromLRUList(this); |
| |
| m_decodedSize = size; |
| |
| if (inCache()) { |
| // Now insert into the new LRU list. |
| cache()->insertInLRUList(this); |
| |
| // Insert into or remove from the live decoded list if necessary. |
| if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients()) |
| cache()->insertInLiveDecodedResourcesList(this); |
| else if (!m_decodedSize && m_inLiveDecodedResourcesList) |
| cache()->removeFromLiveDecodedResourcesList(this); |
| |
| // Update the cache's size totals. |
| cache()->adjustSize(hasClients(), delta); |
| } |
| } |
| |
| void CachedResource::setEncodedSize(unsigned size) |
| { |
| if (size == m_encodedSize) |
| return; |
| |
| // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert. |
| ASSERT(size == 0 || size >= m_encodedSize); |
| |
| int delta = size - m_encodedSize; |
| |
| // The object must now be moved to a different queue, since its size has been changed. |
| // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous |
| // queue. |
| if (inCache()) |
| cache()->removeFromLRUList(this); |
| |
| m_encodedSize = size; |
| |
| if (inCache()) { |
| // Now insert into the new LRU list. |
| cache()->insertInLRUList(this); |
| |
| // Update the cache's size totals. |
| cache()->adjustSize(hasClients(), delta); |
| } |
| } |
| |
| void CachedResource::didAccessDecodedData(double timeStamp) |
| { |
| m_lastDecodedAccessTime = timeStamp; |
| |
| if (inCache()) { |
| if (m_inLiveDecodedResourcesList) { |
| cache()->removeFromLiveDecodedResourcesList(this); |
| cache()->insertInLiveDecodedResourcesList(this); |
| } |
| cache()->prune(); |
| } |
| } |
| |
| void CachedResource::setResourceToRevalidate(CachedResource* resource) |
| { |
| ASSERT(resource); |
| ASSERT(!m_resourceToRevalidate); |
| ASSERT(resource != this); |
| ASSERT(!resource->m_isBeingRevalidated); |
| ASSERT(m_handlesToRevalidate.isEmpty()); |
| ASSERT(resource->type() == type()); |
| resource->m_isBeingRevalidated = true; |
| m_resourceToRevalidate = resource; |
| } |
| |
| void CachedResource::clearResourceToRevalidate() |
| { |
| ASSERT(m_resourceToRevalidate); |
| m_resourceToRevalidate->m_isBeingRevalidated = false; |
| m_resourceToRevalidate->deleteIfPossible(); |
| m_handlesToRevalidate.clear(); |
| m_resourceToRevalidate = 0; |
| deleteIfPossible(); |
| } |
| |
| void CachedResource::switchClientsToRevalidatedResource() |
| { |
| ASSERT(m_resourceToRevalidate); |
| ASSERT(m_resourceToRevalidate->inCache()); |
| ASSERT(!inCache()); |
| |
| HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); |
| for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { |
| CachedResourceHandleBase* handle = *it; |
| handle->m_resource = m_resourceToRevalidate; |
| m_resourceToRevalidate->registerHandle(handle); |
| --m_handleCount; |
| } |
| ASSERT(!m_handleCount); |
| m_handlesToRevalidate.clear(); |
| |
| Vector<CachedResourceClient*> clientsToMove; |
| HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end(); |
| for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) { |
| CachedResourceClient* client = it->first; |
| unsigned count = it->second; |
| while (count) { |
| clientsToMove.append(client); |
| --count; |
| } |
| } |
| // Equivalent of calling removeClient() for all clients |
| m_clients.clear(); |
| |
| unsigned moveCount = clientsToMove.size(); |
| for (unsigned n = 0; n < moveCount; ++n) |
| m_resourceToRevalidate->addClientToSet(clientsToMove[n]); |
| for (unsigned n = 0; n < moveCount; ++n) { |
| // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. |
| if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n])) |
| m_resourceToRevalidate->didAddClient(clientsToMove[n]); |
| } |
| } |
| |
| void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) |
| { |
| m_responseTimestamp = currentTime(); |
| |
| DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-")); |
| // RFC2616 10.3.5 |
| // Update cached headers from the 304 response |
| const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields(); |
| HTTPHeaderMap::const_iterator end = newHeaders.end(); |
| for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) { |
| // Don't allow 304 response to update content headers, these can't change but some servers send wrong values. |
| if (it->first.startsWith(contentHeaderPrefix, false)) |
| continue; |
| m_response.setHTTPHeaderField(it->first, it->second); |
| } |
| } |
| |
| bool CachedResource::canUseCacheValidator() const |
| { |
| if (m_loading || m_errorOccurred) |
| return false; |
| |
| if (m_response.cacheControlContainsNoStore()) |
| return false; |
| |
| DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified")); |
| DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag")); |
| return !m_response.httpHeaderField(lastModifiedHeader).isEmpty() || !m_response.httpHeaderField(eTagHeader).isEmpty(); |
| } |
| |
| bool CachedResource::mustRevalidate(CachePolicy cachePolicy) const |
| { |
| if (m_errorOccurred) |
| return true; |
| |
| if (m_loading) |
| return false; |
| |
| if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) |
| return true; |
| |
| if (cachePolicy == CachePolicyCache) |
| return m_response.cacheControlContainsMustRevalidate() && isExpired(); |
| |
| return isExpired(); |
| } |
| |
| bool CachedResource::isSafeToMakePurgeable() const |
| { |
| return !hasClients() && !m_isBeingRevalidated && !m_resourceToRevalidate; |
| } |
| |
| bool CachedResource::makePurgeable(bool purgeable) |
| { |
| if (purgeable) { |
| ASSERT(isSafeToMakePurgeable()); |
| |
| if (m_purgeableData) { |
| ASSERT(!m_data); |
| return true; |
| } |
| if (!m_data) |
| return false; |
| |
| // Should not make buffer purgeable if it has refs othen than this since we don't want two copies. |
| if (!m_data->hasOneRef()) |
| return false; |
| |
| // Purgeable buffers are allocated in multiples of the page size (4KB in common CPUs) so it does not make sense for very small buffers. |
| const size_t purgeableThreshold = 4 * 4096; |
| if (m_data->size() < purgeableThreshold) |
| return false; |
| |
| if (m_data->hasPurgeableBuffer()) { |
| m_purgeableData.set(m_data->releasePurgeableBuffer()); |
| } else { |
| m_purgeableData.set(PurgeableBuffer::create(m_data->data(), m_data->size())); |
| if (!m_purgeableData) |
| return false; |
| } |
| |
| m_purgeableData->makePurgeable(true); |
| m_data.clear(); |
| return true; |
| } |
| |
| if (!m_purgeableData) |
| return true; |
| ASSERT(!m_data); |
| ASSERT(!hasClients()); |
| |
| if (!m_purgeableData->makePurgeable(false)) |
| return false; |
| |
| m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release()); |
| return true; |
| } |
| |
| bool CachedResource::isPurgeable() const |
| { |
| return m_purgeableData && m_purgeableData->isPurgeable(); |
| } |
| |
| bool CachedResource::wasPurged() const |
| { |
| return m_purgeableData && m_purgeableData->wasPurged(); |
| } |
| |
| unsigned CachedResource::overheadSize() const |
| { |
| return sizeof(CachedResource) + m_response.memoryUsage() + 576; |
| /* |
| 576 = 192 + // average size of m_url |
| 384; // average size of m_clients hash map |
| */ |
| } |
| |
| } |