2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6 Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
25 #include "CachedResource.h"
28 #include "CachedMetadata.h"
29 #include "CachedResourceClient.h"
30 #include "CachedResourceClientWalker.h"
31 #include "CachedResourceHandle.h"
32 #include "CachedResourceLoader.h"
34 #include "FrameLoaderClient.h"
37 #include "PurgeableBuffer.h"
39 #include "ResourceHandle.h"
40 #include "SharedBuffer.h"
41 #include <wtf/CurrentTime.h>
42 #include <wtf/MathExtras.h>
43 #include <wtf/RefCountedLeakCounter.h>
44 #include <wtf/StdLibExtras.h>
45 #include <wtf/Vector.h>
52 static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource");
55 CachedResource::CachedResource(const String& url, Type type)
58 , m_responseTimestamp(currentTime())
59 , m_lastDecodedAccessTime(0)
65 , m_preloadResult(PreloadNotReferenced)
66 , m_inLiveDecodedResourcesList(false)
67 , m_requestedFromNetworkingLayer(false)
68 , m_sendResourceLoadCallbacks(true)
69 , m_errorOccurred(false)
78 , m_nextInAllResourcesList(0)
79 , m_prevInAllResourcesList(0)
80 , m_nextInLiveResourcesList(0)
81 , m_prevInLiveResourcesList(0)
82 , m_cachedResourceLoader(0)
83 , m_resourceToRevalidate(0)
87 cachedResourceLeakCounter.increment();
91 CachedResource::~CachedResource()
93 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this.
97 ASSERT(url().isNull() || cache()->resourceForURL(url()) != this);
100 cachedResourceLeakCounter.decrement();
103 if (m_cachedResourceLoader)
104 m_cachedResourceLoader->removeCachedResource(this);
107 void CachedResource::load(CachedResourceLoader* cachedResourceLoader, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks)
109 m_sendResourceLoadCallbacks = sendResourceLoadCallbacks;
110 cache()->loader()->load(cachedResourceLoader, this, incremental, securityCheck, sendResourceLoadCallbacks);
114 void CachedResource::data(PassRefPtr<SharedBuffer>, bool allDataReceived)
116 if (!allDataReceived)
119 CachedResourceClientWalker w(m_clients);
120 while (CachedResourceClient* c = w.next())
121 c->notifyFinished(this);
124 void CachedResource::finish()
129 bool CachedResource::isExpired() const
131 if (m_response.isNull())
134 return currentAge() > freshnessLifetime();
137 double CachedResource::currentAge() const
140 // No compensation for latency as that is not terribly important in practice
141 double dateValue = m_response.date();
142 double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0;
143 double ageValue = m_response.age();
144 double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge;
145 double residentTime = currentTime() - m_responseTimestamp;
146 return correctedReceivedAge + residentTime;
149 double CachedResource::freshnessLifetime() const
151 // Cache non-http resources liberally
152 if (!m_response.url().protocolInHTTPFamily())
153 return std::numeric_limits<double>::max();
156 double maxAgeValue = m_response.cacheControlMaxAge();
157 if (isfinite(maxAgeValue))
159 double expiresValue = m_response.expires();
160 double dateValue = m_response.date();
161 double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp;
162 if (isfinite(expiresValue))
163 return expiresValue - creationTime;
164 double lastModifiedValue = m_response.lastModified();
165 if (isfinite(lastModifiedValue))
166 return (creationTime - lastModifiedValue) * 0.1;
167 // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0.
171 void CachedResource::setResponse(const ResourceResponse& response)
173 m_response = response;
174 m_responseTimestamp = currentTime();
177 void CachedResource::setSerializedCachedMetadata(const char* data, size_t size)
179 // We only expect to receive cached metadata from the platform once.
180 // If this triggers, it indicates an efficiency problem which is most
181 // likely unexpected in code designed to improve performance.
182 ASSERT(!m_cachedMetadata);
184 m_cachedMetadata = CachedMetadata::deserialize(data, size);
187 void CachedResource::setCachedMetadata(unsigned dataTypeID, const char* data, size_t size)
189 // Currently, only one type of cached metadata per resource is supported.
190 // If the need arises for multiple types of metadata per resource this could
191 // be enhanced to store types of metadata in a map.
192 ASSERT(!m_cachedMetadata);
194 m_cachedMetadata = CachedMetadata::create(dataTypeID, data, size);
195 ResourceHandle::cacheMetadata(m_response, m_cachedMetadata->serialize());
198 CachedMetadata* CachedResource::cachedMetadata(unsigned dataTypeID) const
200 if (!m_cachedMetadata || m_cachedMetadata->dataTypeID() != dataTypeID)
202 return m_cachedMetadata.get();
205 void CachedResource::setRequest(Request* request)
207 if (request && !m_request)
210 if (canDelete() && !inCache())
214 void CachedResource::addClient(CachedResourceClient* client)
216 addClientToSet(client);
217 didAddClient(client);
220 void CachedResource::didAddClient(CachedResourceClient* c)
223 c->notifyFinished(this);
226 void CachedResource::addClientToSet(CachedResourceClient* client)
228 ASSERT(!isPurgeable());
230 if (m_preloadResult == PreloadNotReferenced) {
232 m_preloadResult = PreloadReferencedWhileComplete;
233 else if (m_requestedFromNetworkingLayer)
234 m_preloadResult = PreloadReferencedWhileLoading;
236 m_preloadResult = PreloadReferenced;
238 if (!hasClients() && inCache())
239 cache()->addToLiveResourcesSize(this);
240 m_clients.add(client);
243 void CachedResource::removeClient(CachedResourceClient* client)
245 ASSERT(m_clients.contains(client));
246 m_clients.remove(client);
248 if (canDelete() && !inCache())
250 else if (!hasClients() && inCache()) {
251 cache()->removeFromLiveResourcesSize(this);
252 cache()->removeFromLiveDecodedResourcesList(this);
254 if (response().cacheControlContainsNoStore()) {
256 // "no-store: ...MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible"
257 cache()->remove(this);
261 // This object may be dead here.
264 void CachedResource::deleteIfPossible()
266 if (canDelete() && !inCache())
270 void CachedResource::setDecodedSize(unsigned size)
272 if (size == m_decodedSize)
275 int delta = size - m_decodedSize;
277 // The object must now be moved to a different queue, since its size has been changed.
278 // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous
281 cache()->removeFromLRUList(this);
283 m_decodedSize = size;
286 // Now insert into the new LRU list.
287 cache()->insertInLRUList(this);
289 // Insert into or remove from the live decoded list if necessary.
290 // When inserting into the LiveDecodedResourcesList it is possible
291 // that the m_lastDecodedAccessTime is still zero or smaller than
292 // the m_lastDecodedAccessTime of the current list head. This is a
293 // violation of the invariant that the list is to be kept sorted
294 // by access time. The weakening of the invariant does not pose
295 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209
296 if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients())
297 cache()->insertInLiveDecodedResourcesList(this);
298 else if (!m_decodedSize && m_inLiveDecodedResourcesList)
299 cache()->removeFromLiveDecodedResourcesList(this);
301 // Update the cache's size totals.
302 cache()->adjustSize(hasClients(), delta);
306 void CachedResource::setEncodedSize(unsigned size)
308 if (size == m_encodedSize)
311 // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert.
312 ASSERT(size == 0 || size >= m_encodedSize);
314 int delta = size - m_encodedSize;
316 // The object must now be moved to a different queue, since its size has been changed.
317 // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous
320 cache()->removeFromLRUList(this);
322 m_encodedSize = size;
325 // Now insert into the new LRU list.
326 cache()->insertInLRUList(this);
328 // Update the cache's size totals.
329 cache()->adjustSize(hasClients(), delta);
333 void CachedResource::didAccessDecodedData(double timeStamp)
335 m_lastDecodedAccessTime = timeStamp;
338 if (m_inLiveDecodedResourcesList) {
339 cache()->removeFromLiveDecodedResourcesList(this);
340 cache()->insertInLiveDecodedResourcesList(this);
346 void CachedResource::setResourceToRevalidate(CachedResource* resource)
349 ASSERT(!m_resourceToRevalidate);
350 ASSERT(resource != this);
351 ASSERT(m_handlesToRevalidate.isEmpty());
352 ASSERT(resource->type() == type());
354 LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource);
356 // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances.
357 // https://bugs.webkit.org/show_bug.cgi?id=28604.
358 // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate.
359 ASSERT(!resource->m_proxyResource);
361 resource->m_proxyResource = this;
362 m_resourceToRevalidate = resource;
365 void CachedResource::clearResourceToRevalidate()
367 ASSERT(m_resourceToRevalidate);
368 // A resource may start revalidation before this method has been called, so check that this resource is still the proxy resource before clearing it out.
369 if (m_resourceToRevalidate->m_proxyResource == this) {
370 m_resourceToRevalidate->m_proxyResource = 0;
371 m_resourceToRevalidate->deleteIfPossible();
373 m_handlesToRevalidate.clear();
374 m_resourceToRevalidate = 0;
378 void CachedResource::switchClientsToRevalidatedResource()
380 ASSERT(m_resourceToRevalidate);
381 ASSERT(m_resourceToRevalidate->inCache());
384 LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate);
386 HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end();
387 for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) {
388 CachedResourceHandleBase* handle = *it;
389 handle->m_resource = m_resourceToRevalidate;
390 m_resourceToRevalidate->registerHandle(handle);
393 ASSERT(!m_handleCount);
394 m_handlesToRevalidate.clear();
396 Vector<CachedResourceClient*> clientsToMove;
397 HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end();
398 for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) {
399 CachedResourceClient* client = it->first;
400 unsigned count = it->second;
402 clientsToMove.append(client);
406 // Equivalent of calling removeClient() for all clients
409 unsigned moveCount = clientsToMove.size();
410 for (unsigned n = 0; n < moveCount; ++n)
411 m_resourceToRevalidate->addClientToSet(clientsToMove[n]);
412 for (unsigned n = 0; n < moveCount; ++n) {
413 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore.
414 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n]))
415 m_resourceToRevalidate->didAddClient(clientsToMove[n]);
419 void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse)
421 m_responseTimestamp = currentTime();
423 DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-"));
425 // Update cached headers from the 304 response
426 const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields();
427 HTTPHeaderMap::const_iterator end = newHeaders.end();
428 for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) {
429 // Don't allow 304 response to update content headers, these can't change but some servers send wrong values.
430 if (it->first.startsWith(contentHeaderPrefix, false))
432 m_response.setHTTPHeaderField(it->first, it->second);
436 void CachedResource::registerHandle(CachedResourceHandleBase* h)
439 if (m_resourceToRevalidate)
440 m_handlesToRevalidate.add(h);
443 void CachedResource::unregisterHandle(CachedResourceHandleBase* h)
445 ASSERT(m_handleCount > 0);
448 if (m_resourceToRevalidate)
449 m_handlesToRevalidate.remove(h);
455 bool CachedResource::canUseCacheValidator() const
457 if (m_loading || m_errorOccurred)
460 if (m_response.cacheControlContainsNoStore())
463 DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified"));
464 DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag"));
465 return !m_response.httpHeaderField(lastModifiedHeader).isEmpty() || !m_response.httpHeaderField(eTagHeader).isEmpty();
468 bool CachedResource::mustRevalidate(CachePolicy cachePolicy) const
470 if (m_errorOccurred) {
471 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_errorOccurred\n", this);
478 if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) {
479 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this);
483 if (cachePolicy == CachePolicyCache) {
484 if (m_response.cacheControlContainsMustRevalidate() && isExpired()) {
485 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this);
492 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this);
499 bool CachedResource::isSafeToMakePurgeable() const
501 return !hasClients() && !m_proxyResource && !m_resourceToRevalidate;
504 bool CachedResource::makePurgeable(bool purgeable)
507 ASSERT(isSafeToMakePurgeable());
509 if (m_purgeableData) {
516 // Should not make buffer purgeable if it has refs other than this since we don't want two copies.
517 if (!m_data->hasOneRef())
520 // Purgeable buffers are allocated in multiples of the page size (4KB in common CPUs) so it does not make sense for very small buffers.
521 const size_t purgeableThreshold = 4 * 4096;
522 if (m_data->size() < purgeableThreshold)
525 if (m_data->hasPurgeableBuffer()) {
526 m_purgeableData = m_data->releasePurgeableBuffer();
528 m_purgeableData = PurgeableBuffer::create(m_data->data(), m_data->size());
529 if (!m_purgeableData)
533 m_purgeableData->makePurgeable(true);
538 if (!m_purgeableData)
541 ASSERT(!hasClients());
543 if (!m_purgeableData->makePurgeable(false))
546 m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release());
550 bool CachedResource::isPurgeable() const
552 return m_purgeableData && m_purgeableData->isPurgeable();
555 bool CachedResource::wasPurged() const
557 return m_purgeableData && m_purgeableData->wasPurged();
560 unsigned CachedResource::overheadSize() const
562 return sizeof(CachedResource) + m_response.memoryUsage() + 576;
564 576 = 192 + // average size of m_url
565 384; // average size of m_clients hash map