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"
27 #include "MemoryCache.h"
28 #include "CachedMetadata.h"
29 #include "CachedResourceClient.h"
30 #include "CachedResourceClientWalker.h"
31 #include "CachedResourceHandle.h"
32 #include "CachedResourceLoader.h"
33 #include "CachedResourceRequest.h"
34 #include "CrossOriginAccessControl.h"
36 #include "FrameLoaderClient.h"
39 #include "PurgeableBuffer.h"
40 #include "ResourceHandle.h"
41 #include "SharedBuffer.h"
42 #include <wtf/CurrentTime.h>
43 #include <wtf/MathExtras.h>
44 #include <wtf/RefCountedLeakCounter.h>
45 #include <wtf/StdLibExtras.h>
46 #include <wtf/Vector.h>
52 static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type)
55 case CachedResource::CSSStyleSheet:
57 case CachedResource::XSLStyleSheet:
59 return ResourceLoadPriorityHigh;
60 case CachedResource::Script:
61 case CachedResource::FontResource:
62 return ResourceLoadPriorityMedium;
63 case CachedResource::ImageResource:
64 return ResourceLoadPriorityLow;
65 #if ENABLE(LINK_PREFETCH)
66 case CachedResource::LinkPrefetch:
67 return ResourceLoadPriorityVeryLow;
68 case CachedResource::LinkPrerender:
69 return ResourceLoadPriorityVeryLow;
70 case CachedResource::LinkSubresource:
71 return ResourceLoadPriorityVeryLow;
75 return ResourceLoadPriorityLow;
79 static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource");
82 CachedResource::CachedResource(const ResourceRequest& request, Type type)
83 : m_resourceRequest(request)
84 , m_loadPriority(defaultPriorityForResourceType(type))
85 , m_responseTimestamp(currentTime())
86 , m_lastDecodedAccessTime(0)
92 , m_preloadResult(PreloadNotReferenced)
93 , m_inLiveDecodedResourcesList(false)
94 , m_requestedFromNetworkingLayer(false)
99 , m_options(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials)
104 , m_nextInAllResourcesList(0)
105 , m_prevInAllResourcesList(0)
106 , m_nextInLiveResourcesList(0)
107 , m_prevInLiveResourcesList(0)
108 , m_owningCachedResourceLoader(0)
109 , m_resourceToRevalidate(0)
113 cachedResourceLeakCounter.increment();
117 CachedResource::~CachedResource()
119 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this.
123 ASSERT(url().isNull() || memoryCache()->resourceForURL(KURL(ParsedURLString, url())) != this);
127 cachedResourceLeakCounter.decrement();
130 if (m_owningCachedResourceLoader)
131 m_owningCachedResourceLoader->removeCachedResource(this);
134 void CachedResource::load(CachedResourceLoader* cachedResourceLoader, bool incremental, SecurityCheckPolicy securityCheck)
137 m_request = CachedResourceRequest::load(cachedResourceLoader, this, incremental, securityCheck, m_options);
140 cachedResourceLoader->incrementRequestCount(this);
144 void CachedResource::checkNotify()
149 CachedResourceClientWalker w(m_clients);
150 while (CachedResourceClient* c = w.next())
151 c->notifyFinished(this);
154 void CachedResource::data(PassRefPtr<SharedBuffer>, bool allDataReceived)
156 if (!allDataReceived)
163 void CachedResource::error(CachedResource::Status status)
166 ASSERT(errorOccurred());
173 void CachedResource::finish()
178 bool CachedResource::passesAccessControlCheck(SecurityOrigin* securityOrigin)
180 String errorDescription;
181 return WebCore::passesAccessControlCheck(m_response, resourceRequest().allowCookies() ? AllowStoredCredentials : DoNotAllowStoredCredentials, securityOrigin, errorDescription);
184 bool CachedResource::isExpired() const
186 if (m_response.isNull())
189 return currentAge() > freshnessLifetime();
192 double CachedResource::currentAge() const
195 // No compensation for latency as that is not terribly important in practice
196 double dateValue = m_response.date();
197 double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0;
198 double ageValue = m_response.age();
199 double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge;
200 double residentTime = currentTime() - m_responseTimestamp;
201 return correctedReceivedAge + residentTime;
204 double CachedResource::freshnessLifetime() const
206 // Cache non-http resources liberally
207 if (!m_response.url().protocolInHTTPFamily())
208 return std::numeric_limits<double>::max();
211 double maxAgeValue = m_response.cacheControlMaxAge();
212 if (isfinite(maxAgeValue))
214 double expiresValue = m_response.expires();
215 double dateValue = m_response.date();
216 double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp;
217 if (isfinite(expiresValue))
218 return expiresValue - creationTime;
219 double lastModifiedValue = m_response.lastModified();
220 if (isfinite(lastModifiedValue))
221 return (creationTime - lastModifiedValue) * 0.1;
222 // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0.
226 void CachedResource::setResponse(const ResourceResponse& response)
228 m_response = response;
229 m_responseTimestamp = currentTime();
232 void CachedResource::setSerializedCachedMetadata(const char* data, size_t size)
234 // We only expect to receive cached metadata from the platform once.
235 // If this triggers, it indicates an efficiency problem which is most
236 // likely unexpected in code designed to improve performance.
237 ASSERT(!m_cachedMetadata);
239 m_cachedMetadata = CachedMetadata::deserialize(data, size);
242 void CachedResource::setCachedMetadata(unsigned dataTypeID, const char* data, size_t size)
244 // Currently, only one type of cached metadata per resource is supported.
245 // If the need arises for multiple types of metadata per resource this could
246 // be enhanced to store types of metadata in a map.
247 ASSERT(!m_cachedMetadata);
249 m_cachedMetadata = CachedMetadata::create(dataTypeID, data, size);
250 ResourceHandle::cacheMetadata(m_response, m_cachedMetadata->serialize());
253 CachedMetadata* CachedResource::cachedMetadata(unsigned dataTypeID) const
255 if (!m_cachedMetadata || m_cachedMetadata->dataTypeID() != dataTypeID)
257 return m_cachedMetadata.get();
260 void CachedResource::stopLoading()
265 CachedResourceHandle<CachedResource> protect(this);
267 // All loads finish with data(allDataReceived = true) or error(), except for
268 // canceled loads, which silently set our request to 0. Be sure to notify our
269 // client in that case, so we don't seem to continue loading forever.
277 void CachedResource::addClient(CachedResourceClient* client)
279 addClientToSet(client);
280 didAddClient(client);
283 void CachedResource::didAddClient(CachedResourceClient* c)
286 c->notifyFinished(this);
289 void CachedResource::addClientToSet(CachedResourceClient* client)
291 ASSERT(!isPurgeable());
293 if (m_preloadResult == PreloadNotReferenced) {
295 m_preloadResult = PreloadReferencedWhileComplete;
296 else if (m_requestedFromNetworkingLayer)
297 m_preloadResult = PreloadReferencedWhileLoading;
299 m_preloadResult = PreloadReferenced;
301 if (!hasClients() && inCache())
302 memoryCache()->addToLiveResourcesSize(this);
303 m_clients.add(client);
306 void CachedResource::removeClient(CachedResourceClient* client)
308 ASSERT(m_clients.contains(client));
309 m_clients.remove(client);
311 if (canDelete() && !inCache())
313 else if (!hasClients() && inCache()) {
314 memoryCache()->removeFromLiveResourcesSize(this);
315 memoryCache()->removeFromLiveDecodedResourcesList(this);
317 if (response().cacheControlContainsNoStore()) {
319 // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible"
320 // "... History buffers MAY store such responses as part of their normal operation."
321 // We allow non-secure content to be reused in history, but we do not allow secure content to be reused.
322 if (protocolIs(url(), "https"))
323 memoryCache()->remove(this);
325 memoryCache()->prune();
327 // This object may be dead here.
330 void CachedResource::deleteIfPossible()
332 if (canDelete() && !inCache())
336 void CachedResource::setDecodedSize(unsigned size)
338 if (size == m_decodedSize)
341 int delta = size - m_decodedSize;
343 // The object must now be moved to a different queue, since its size has been changed.
344 // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous
347 memoryCache()->removeFromLRUList(this);
349 m_decodedSize = size;
352 // Now insert into the new LRU list.
353 memoryCache()->insertInLRUList(this);
355 // Insert into or remove from the live decoded list if necessary.
356 // When inserting into the LiveDecodedResourcesList it is possible
357 // that the m_lastDecodedAccessTime is still zero or smaller than
358 // the m_lastDecodedAccessTime of the current list head. This is a
359 // violation of the invariant that the list is to be kept sorted
360 // by access time. The weakening of the invariant does not pose
361 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209
362 if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients())
363 memoryCache()->insertInLiveDecodedResourcesList(this);
364 else if (!m_decodedSize && m_inLiveDecodedResourcesList)
365 memoryCache()->removeFromLiveDecodedResourcesList(this);
367 // Update the cache's size totals.
368 memoryCache()->adjustSize(hasClients(), delta);
372 void CachedResource::setEncodedSize(unsigned size)
374 if (size == m_encodedSize)
377 // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert.
378 ASSERT(size == 0 || size >= m_encodedSize);
380 int delta = size - m_encodedSize;
382 // The object must now be moved to a different queue, since its size has been changed.
383 // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous
386 memoryCache()->removeFromLRUList(this);
388 m_encodedSize = size;
391 // Now insert into the new LRU list.
392 memoryCache()->insertInLRUList(this);
394 // Update the cache's size totals.
395 memoryCache()->adjustSize(hasClients(), delta);
399 void CachedResource::didAccessDecodedData(double timeStamp)
401 m_lastDecodedAccessTime = timeStamp;
404 if (m_inLiveDecodedResourcesList) {
405 memoryCache()->removeFromLiveDecodedResourcesList(this);
406 memoryCache()->insertInLiveDecodedResourcesList(this);
408 memoryCache()->prune();
412 void CachedResource::setResourceToRevalidate(CachedResource* resource)
415 ASSERT(!m_resourceToRevalidate);
416 ASSERT(resource != this);
417 ASSERT(m_handlesToRevalidate.isEmpty());
418 ASSERT(resource->type() == type());
420 LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource);
422 // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances.
423 // https://bugs.webkit.org/show_bug.cgi?id=28604.
424 // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate.
425 ASSERT(!resource->m_proxyResource);
427 resource->m_proxyResource = this;
428 m_resourceToRevalidate = resource;
431 void CachedResource::clearResourceToRevalidate()
433 ASSERT(m_resourceToRevalidate);
434 // 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.
435 if (m_resourceToRevalidate->m_proxyResource == this) {
436 m_resourceToRevalidate->m_proxyResource = 0;
437 m_resourceToRevalidate->deleteIfPossible();
439 m_handlesToRevalidate.clear();
440 m_resourceToRevalidate = 0;
444 void CachedResource::switchClientsToRevalidatedResource()
446 ASSERT(m_resourceToRevalidate);
447 ASSERT(m_resourceToRevalidate->inCache());
450 LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate);
452 HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end();
453 for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) {
454 CachedResourceHandleBase* handle = *it;
455 handle->m_resource = m_resourceToRevalidate;
456 m_resourceToRevalidate->registerHandle(handle);
459 ASSERT(!m_handleCount);
460 m_handlesToRevalidate.clear();
462 Vector<CachedResourceClient*> clientsToMove;
463 HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end();
464 for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) {
465 CachedResourceClient* client = it->first;
466 unsigned count = it->second;
468 clientsToMove.append(client);
472 // Equivalent of calling removeClient() for all clients
475 unsigned moveCount = clientsToMove.size();
476 for (unsigned n = 0; n < moveCount; ++n)
477 m_resourceToRevalidate->addClientToSet(clientsToMove[n]);
478 for (unsigned n = 0; n < moveCount; ++n) {
479 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore.
480 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n]))
481 m_resourceToRevalidate->didAddClient(clientsToMove[n]);
485 void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse)
487 m_responseTimestamp = currentTime();
489 DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-"));
491 // Update cached headers from the 304 response
492 const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields();
493 HTTPHeaderMap::const_iterator end = newHeaders.end();
494 for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) {
495 // Don't allow 304 response to update content headers, these can't change but some servers send wrong values.
496 if (it->first.startsWith(contentHeaderPrefix, false))
498 m_response.setHTTPHeaderField(it->first, it->second);
502 void CachedResource::registerHandle(CachedResourceHandleBase* h)
505 if (m_resourceToRevalidate)
506 m_handlesToRevalidate.add(h);
509 void CachedResource::unregisterHandle(CachedResourceHandleBase* h)
511 ASSERT(m_handleCount > 0);
514 if (m_resourceToRevalidate)
515 m_handlesToRevalidate.remove(h);
521 bool CachedResource::canUseCacheValidator() const
523 if (m_loading || errorOccurred())
526 if (m_response.cacheControlContainsNoStore())
528 return m_response.hasCacheValidatorFields();
531 bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const
533 ASSERT(cachePolicy == CachePolicyRevalidate || cachePolicy == CachePolicyCache || cachePolicy == CachePolicyVerify);
535 if (cachePolicy == CachePolicyRevalidate)
538 if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) {
539 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this);
543 if (cachePolicy == CachePolicyCache) {
544 if (m_response.cacheControlContainsMustRevalidate() && isExpired()) {
545 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this);
553 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this);
560 bool CachedResource::isSafeToMakePurgeable() const
562 return !hasClients() && !m_proxyResource && !m_resourceToRevalidate;
565 bool CachedResource::makePurgeable(bool purgeable)
568 ASSERT(isSafeToMakePurgeable());
570 if (m_purgeableData) {
577 // Should not make buffer purgeable if it has refs other than this since we don't want two copies.
578 if (!m_data->hasOneRef())
581 if (m_data->hasPurgeableBuffer()) {
582 m_purgeableData = m_data->releasePurgeableBuffer();
584 m_purgeableData = PurgeableBuffer::create(m_data->data(), m_data->size());
585 if (!m_purgeableData)
587 m_purgeableData->setPurgePriority(purgePriority());
590 m_purgeableData->makePurgeable(true);
595 if (!m_purgeableData)
598 ASSERT(!hasClients());
600 if (!m_purgeableData->makePurgeable(false))
603 m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release());
607 bool CachedResource::isPurgeable() const
609 return m_purgeableData && m_purgeableData->isPurgeable();
612 bool CachedResource::wasPurged() const
614 return m_purgeableData && m_purgeableData->wasPurged();
617 unsigned CachedResource::overheadSize() const
619 static const int kAverageClientsHashMapSize = 384;
620 return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2;
623 void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority)
625 if (loadPriority == ResourceLoadPriorityUnresolved)
627 m_loadPriority = loadPriority;