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, 2009, 2010, 2011 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 "CrossOriginAccessControl.h"
36 #include "FrameLoaderClient.h"
39 #include "PurgeableBuffer.h"
40 #include "ResourceHandle.h"
41 #include "ResourceLoadScheduler.h"
42 #include "SharedBuffer.h"
43 #include "SubresourceLoader.h"
44 #include <wtf/CurrentTime.h>
45 #include <wtf/MathExtras.h>
46 #include <wtf/RefCountedLeakCounter.h>
47 #include <wtf/StdLibExtras.h>
48 #include <wtf/Vector.h>
54 static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type)
57 case CachedResource::CSSStyleSheet:
59 case CachedResource::XSLStyleSheet:
61 return ResourceLoadPriorityHigh;
62 case CachedResource::Script:
63 case CachedResource::FontResource:
64 case CachedResource::RawResource:
65 return ResourceLoadPriorityMedium;
66 case CachedResource::ImageResource:
67 return ResourceLoadPriorityLow;
68 #if ENABLE(LINK_PREFETCH)
69 case CachedResource::LinkPrefetch:
70 return ResourceLoadPriorityVeryLow;
71 case CachedResource::LinkPrerender:
72 return ResourceLoadPriorityVeryLow;
73 case CachedResource::LinkSubresource:
74 return ResourceLoadPriorityVeryLow;
76 #if ENABLE(VIDEO_TRACK)
77 case CachedResource::TextTrackResource:
78 return ResourceLoadPriorityLow;
80 #if ENABLE(CSS_SHADERS)
81 case CachedResource::ShaderResource:
82 return ResourceLoadPriorityMedium;
86 return ResourceLoadPriorityLow;
89 #if PLATFORM(CHROMIUM)
90 static ResourceRequest::TargetType cachedResourceTypeToTargetType(CachedResource::Type type)
93 case CachedResource::CSSStyleSheet:
95 case CachedResource::XSLStyleSheet:
97 return ResourceRequest::TargetIsStyleSheet;
98 case CachedResource::Script:
99 return ResourceRequest::TargetIsScript;
100 case CachedResource::FontResource:
101 return ResourceRequest::TargetIsFontResource;
102 case CachedResource::ImageResource:
103 return ResourceRequest::TargetIsImage;
104 case CachedResource::RawResource:
105 return ResourceRequest::TargetIsSubresource;
106 #if ENABLE(LINK_PREFETCH)
107 case CachedResource::LinkPrefetch:
108 return ResourceRequest::TargetIsPrefetch;
109 case CachedResource::LinkPrerender:
110 return ResourceRequest::TargetIsPrerender;
111 case CachedResource::LinkSubresource:
112 return ResourceRequest::TargetIsSubresource;
114 #if ENABLE(VIDEO_TRACK)
115 case CachedResource::TextTrackResource:
116 return ResourceRequest::TargetIsTextTrack;
119 ASSERT_NOT_REACHED();
120 return ResourceRequest::TargetIsSubresource;
124 DEFINE_DEBUG_ONLY_GLOBAL(RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource"));
126 CachedResource::CachedResource(const ResourceRequest& request, Type type)
127 : m_resourceRequest(request)
128 , m_loadPriority(defaultPriorityForResourceType(type))
129 , m_responseTimestamp(currentTime())
130 , m_lastDecodedAccessTime(0)
131 , m_loadFinishTime(0)
137 , m_preloadResult(PreloadNotReferenced)
138 , m_inLiveDecodedResourcesList(false)
139 , m_requestedFromNetworkingLayer(false)
148 , m_nextInAllResourcesList(0)
149 , m_prevInAllResourcesList(0)
150 , m_nextInLiveResourcesList(0)
151 , m_prevInLiveResourcesList(0)
152 , m_owningCachedResourceLoader(0)
153 , m_resourceToRevalidate(0)
156 ASSERT(m_type == unsigned(type)); // m_type is a bitfield, so this tests careless updates of the enum.
158 cachedResourceLeakCounter.increment();
162 CachedResource::~CachedResource()
164 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this.
168 ASSERT(url().isNull() || memoryCache()->resourceForURL(KURL(ParsedURLString, url())) != this);
172 cachedResourceLeakCounter.decrement();
175 if (m_owningCachedResourceLoader)
176 m_owningCachedResourceLoader->removeCachedResource(this);
179 void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options)
184 #if PLATFORM(CHROMIUM)
185 if (m_resourceRequest.targetType() == ResourceRequest::TargetIsUnspecified)
186 m_resourceRequest.setTargetType(cachedResourceTypeToTargetType(type()));
189 if (!accept().isEmpty())
190 m_resourceRequest.setHTTPAccept(accept());
192 if (isCacheValidator()) {
193 CachedResource* resourceToRevalidate = m_resourceToRevalidate;
194 ASSERT(resourceToRevalidate->canUseCacheValidator());
195 ASSERT(resourceToRevalidate->isLoaded());
196 const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified");
197 const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag");
198 if (!lastModified.isEmpty() || !eTag.isEmpty()) {
199 ASSERT(cachedResourceLoader->cachePolicy() != CachePolicyReload);
200 if (cachedResourceLoader->cachePolicy() == CachePolicyRevalidate)
201 m_resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0");
202 if (!lastModified.isEmpty())
203 m_resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified);
205 m_resourceRequest.setHTTPHeaderField("If-None-Match", eTag);
209 #if ENABLE(LINK_PREFETCH)
210 if (type() == CachedResource::LinkPrefetch || type() == CachedResource::LinkPrerender || type() == CachedResource::LinkSubresource)
211 m_resourceRequest.setHTTPHeaderField("Purpose", "prefetch");
213 m_resourceRequest.setPriority(loadPriority());
215 m_loader = resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->document()->frame(), this, m_resourceRequest, m_resourceRequest.priority(), options);
216 if (!m_loader || m_loader->reachedTerminalState()) {
217 // FIXME: What if resources in other frames were waiting for this revalidation?
218 LOG(ResourceLoading, "Cannot start loading '%s'", url().string().latin1().data());
219 if (m_resourceToRevalidate)
220 memoryCache()->revalidationFailed(this);
221 error(CachedResource::LoadError);
226 cachedResourceLoader->incrementRequestCount(this);
229 void CachedResource::checkNotify()
234 CachedResourceClientWalker<CachedResourceClient> w(m_clients);
235 while (CachedResourceClient* c = w.next())
236 c->notifyFinished(this);
239 void CachedResource::data(PassRefPtr<SharedBuffer>, bool allDataReceived)
241 if (!allDataReceived)
248 void CachedResource::error(CachedResource::Status status)
251 ASSERT(errorOccurred());
258 void CachedResource::finish()
260 if (!errorOccurred())
264 bool CachedResource::passesAccessControlCheck(SecurityOrigin* securityOrigin)
266 String errorDescription;
267 return WebCore::passesAccessControlCheck(m_response, resourceRequest().allowCookies() ? AllowStoredCredentials : DoNotAllowStoredCredentials, securityOrigin, errorDescription);
270 bool CachedResource::isExpired() const
272 if (m_response.isNull())
275 return currentAge() > freshnessLifetime();
278 double CachedResource::currentAge() const
281 // No compensation for latency as that is not terribly important in practice
282 double dateValue = m_response.date();
283 double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0;
284 double ageValue = m_response.age();
285 double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge;
286 double residentTime = currentTime() - m_responseTimestamp;
287 return correctedReceivedAge + residentTime;
290 double CachedResource::freshnessLifetime() const
292 // Cache non-http resources liberally
293 if (!m_response.url().protocolInHTTPFamily())
294 return std::numeric_limits<double>::max();
297 double maxAgeValue = m_response.cacheControlMaxAge();
298 if (isfinite(maxAgeValue))
300 double expiresValue = m_response.expires();
301 double dateValue = m_response.date();
302 double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp;
303 if (isfinite(expiresValue))
304 return expiresValue - creationTime;
305 double lastModifiedValue = m_response.lastModified();
306 if (isfinite(lastModifiedValue))
307 return (creationTime - lastModifiedValue) * 0.1;
308 // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0.
312 void CachedResource::setResponse(const ResourceResponse& response)
314 m_response = response;
315 m_responseTimestamp = currentTime();
316 String encoding = response.textEncodingName();
317 if (!encoding.isNull())
318 setEncoding(encoding);
321 void CachedResource::setSerializedCachedMetadata(const char* data, size_t size)
323 // We only expect to receive cached metadata from the platform once.
324 // If this triggers, it indicates an efficiency problem which is most
325 // likely unexpected in code designed to improve performance.
326 ASSERT(!m_cachedMetadata);
328 m_cachedMetadata = CachedMetadata::deserialize(data, size);
331 void CachedResource::setCachedMetadata(unsigned dataTypeID, const char* data, size_t size)
333 // Currently, only one type of cached metadata per resource is supported.
334 // If the need arises for multiple types of metadata per resource this could
335 // be enhanced to store types of metadata in a map.
336 ASSERT(!m_cachedMetadata);
338 m_cachedMetadata = CachedMetadata::create(dataTypeID, data, size);
339 ResourceHandle::cacheMetadata(m_response, m_cachedMetadata->serialize());
342 CachedMetadata* CachedResource::cachedMetadata(unsigned dataTypeID) const
344 if (!m_cachedMetadata || m_cachedMetadata->dataTypeID() != dataTypeID)
346 return m_cachedMetadata.get();
349 void CachedResource::stopLoading()
354 CachedResourceHandle<CachedResource> protect(this);
356 // All loads finish with data(allDataReceived = true) or error(), except for
357 // canceled loads, which silently set our request to 0. Be sure to notify our
358 // client in that case, so we don't seem to continue loading forever.
366 void CachedResource::addClient(CachedResourceClient* client)
368 addClientToSet(client);
369 didAddClient(client);
372 void CachedResource::didAddClient(CachedResourceClient* c)
375 c->notifyFinished(this);
378 void CachedResource::addClientToSet(CachedResourceClient* client)
380 ASSERT(!isPurgeable());
382 if (m_preloadResult == PreloadNotReferenced) {
384 m_preloadResult = PreloadReferencedWhileComplete;
385 else if (m_requestedFromNetworkingLayer)
386 m_preloadResult = PreloadReferencedWhileLoading;
388 m_preloadResult = PreloadReferenced;
390 if (!hasClients() && inCache())
391 memoryCache()->addToLiveResourcesSize(this);
392 m_clients.add(client);
395 void CachedResource::removeClient(CachedResourceClient* client)
397 ASSERT(m_clients.contains(client));
398 m_clients.remove(client);
400 if (canDelete() && !inCache())
402 else if (!hasClients() && inCache()) {
403 memoryCache()->removeFromLiveResourcesSize(this);
404 memoryCache()->removeFromLiveDecodedResourcesList(this);
406 if (response().cacheControlContainsNoStore()) {
408 // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible"
409 // "... History buffers MAY store such responses as part of their normal operation."
410 // We allow non-secure content to be reused in history, but we do not allow secure content to be reused.
411 if (protocolIs(url(), "https"))
412 memoryCache()->remove(this);
414 memoryCache()->prune();
416 // This object may be dead here.
419 void CachedResource::deleteIfPossible()
421 if (canDelete() && !inCache())
425 void CachedResource::setDecodedSize(unsigned size)
427 if (size == m_decodedSize)
430 int delta = size - m_decodedSize;
432 // The object must now be moved to a different queue, since its size has been changed.
433 // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous
436 memoryCache()->removeFromLRUList(this);
438 m_decodedSize = size;
441 // Now insert into the new LRU list.
442 memoryCache()->insertInLRUList(this);
444 // Insert into or remove from the live decoded list if necessary.
445 // When inserting into the LiveDecodedResourcesList it is possible
446 // that the m_lastDecodedAccessTime is still zero or smaller than
447 // the m_lastDecodedAccessTime of the current list head. This is a
448 // violation of the invariant that the list is to be kept sorted
449 // by access time. The weakening of the invariant does not pose
450 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209
451 if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients())
452 memoryCache()->insertInLiveDecodedResourcesList(this);
453 else if (!m_decodedSize && m_inLiveDecodedResourcesList)
454 memoryCache()->removeFromLiveDecodedResourcesList(this);
456 // Update the cache's size totals.
457 memoryCache()->adjustSize(hasClients(), delta);
461 void CachedResource::setEncodedSize(unsigned size)
463 if (size == m_encodedSize)
466 // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert.
467 ASSERT(size == 0 || size >= m_encodedSize);
469 int delta = size - m_encodedSize;
471 // The object must now be moved to a different queue, since its size has been changed.
472 // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous
475 memoryCache()->removeFromLRUList(this);
477 m_encodedSize = size;
480 // Now insert into the new LRU list.
481 memoryCache()->insertInLRUList(this);
483 // Update the cache's size totals.
484 memoryCache()->adjustSize(hasClients(), delta);
488 void CachedResource::didAccessDecodedData(double timeStamp)
490 m_lastDecodedAccessTime = timeStamp;
493 if (m_inLiveDecodedResourcesList) {
494 memoryCache()->removeFromLiveDecodedResourcesList(this);
495 memoryCache()->insertInLiveDecodedResourcesList(this);
497 memoryCache()->prune();
501 void CachedResource::setResourceToRevalidate(CachedResource* resource)
504 ASSERT(!m_resourceToRevalidate);
505 ASSERT(resource != this);
506 ASSERT(m_handlesToRevalidate.isEmpty());
507 ASSERT(resource->type() == type());
509 LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource);
511 // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances.
512 // https://bugs.webkit.org/show_bug.cgi?id=28604.
513 // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate.
514 ASSERT(!resource->m_proxyResource);
516 resource->m_proxyResource = this;
517 m_resourceToRevalidate = resource;
520 void CachedResource::clearResourceToRevalidate()
522 ASSERT(m_resourceToRevalidate);
523 // 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.
524 if (m_resourceToRevalidate->m_proxyResource == this) {
525 m_resourceToRevalidate->m_proxyResource = 0;
526 m_resourceToRevalidate->deleteIfPossible();
528 m_handlesToRevalidate.clear();
529 m_resourceToRevalidate = 0;
533 void CachedResource::switchClientsToRevalidatedResource()
535 ASSERT(m_resourceToRevalidate);
536 ASSERT(m_resourceToRevalidate->inCache());
539 LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate);
541 HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end();
542 for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) {
543 CachedResourceHandleBase* handle = *it;
544 handle->m_resource = m_resourceToRevalidate;
545 m_resourceToRevalidate->registerHandle(handle);
548 ASSERT(!m_handleCount);
549 m_handlesToRevalidate.clear();
551 Vector<CachedResourceClient*> clientsToMove;
552 HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end();
553 for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) {
554 CachedResourceClient* client = it->first;
555 unsigned count = it->second;
557 clientsToMove.append(client);
561 // Equivalent of calling removeClient() for all clients
564 unsigned moveCount = clientsToMove.size();
565 for (unsigned n = 0; n < moveCount; ++n)
566 m_resourceToRevalidate->addClientToSet(clientsToMove[n]);
567 for (unsigned n = 0; n < moveCount; ++n) {
568 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore.
569 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n]))
570 m_resourceToRevalidate->didAddClient(clientsToMove[n]);
574 void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse)
576 m_responseTimestamp = currentTime();
578 DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-"));
580 // Update cached headers from the 304 response
581 const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields();
582 HTTPHeaderMap::const_iterator end = newHeaders.end();
583 for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) {
584 // Don't allow 304 response to update content headers, these can't change but some servers send wrong values.
585 if (it->first.startsWith(contentHeaderPrefix, false))
587 m_response.setHTTPHeaderField(it->first, it->second);
591 void CachedResource::registerHandle(CachedResourceHandleBase* h)
594 if (m_resourceToRevalidate)
595 m_handlesToRevalidate.add(h);
598 void CachedResource::unregisterHandle(CachedResourceHandleBase* h)
600 ASSERT(m_handleCount > 0);
603 if (m_resourceToRevalidate)
604 m_handlesToRevalidate.remove(h);
610 bool CachedResource::canUseCacheValidator() const
612 if (m_loading || errorOccurred())
615 if (m_response.cacheControlContainsNoStore())
617 return m_response.hasCacheValidatorFields();
620 bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const
622 ASSERT(cachePolicy == CachePolicyRevalidate || cachePolicy == CachePolicyCache || cachePolicy == CachePolicyVerify);
624 if (cachePolicy == CachePolicyRevalidate)
627 if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) {
628 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this);
632 if (cachePolicy == CachePolicyCache) {
633 if (m_response.cacheControlContainsMustRevalidate() && isExpired()) {
634 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this);
642 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this);
649 bool CachedResource::isSafeToMakePurgeable() const
651 return !hasClients() && !m_proxyResource && !m_resourceToRevalidate;
654 bool CachedResource::makePurgeable(bool purgeable)
657 ASSERT(isSafeToMakePurgeable());
659 if (m_purgeableData) {
666 // Should not make buffer purgeable if it has refs other than this since we don't want two copies.
667 if (!m_data->hasOneRef())
670 if (m_data->hasPurgeableBuffer()) {
671 m_purgeableData = m_data->releasePurgeableBuffer();
673 m_purgeableData = PurgeableBuffer::create(m_data->data(), m_data->size());
674 if (!m_purgeableData)
676 m_purgeableData->setPurgePriority(purgePriority());
679 m_purgeableData->makePurgeable(true);
684 if (!m_purgeableData)
687 ASSERT(!hasClients());
689 if (!m_purgeableData->makePurgeable(false))
692 m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release());
696 bool CachedResource::isPurgeable() const
698 return m_purgeableData && m_purgeableData->isPurgeable();
701 bool CachedResource::wasPurged() const
703 return m_purgeableData && m_purgeableData->wasPurged();
706 unsigned CachedResource::overheadSize() const
708 static const int kAverageClientsHashMapSize = 384;
709 return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2;
712 void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority)
714 if (loadPriority == ResourceLoadPriorityUnresolved)
716 m_loadPriority = loadPriority;