CachedResource.cpp revision 2fc2651226baac27029e38c9d6ef883fa32084db
1/* 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. 7 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. 12 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. 17 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. 22*/ 23 24#include "config.h" 25#include "CachedResource.h" 26 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 "Frame.h" 35#include "FrameLoaderClient.h" 36#include "KURL.h" 37#include "Logging.h" 38#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> 46 47using namespace WTF; 48 49namespace WebCore { 50 51static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type) 52{ 53 switch (type) { 54 case CachedResource::CSSStyleSheet: 55#if ENABLE(XSLT) 56 case CachedResource::XSLStyleSheet: 57#endif 58 return ResourceLoadPriorityHigh; 59 case CachedResource::Script: 60 case CachedResource::FontResource: 61 return ResourceLoadPriorityMedium; 62 case CachedResource::ImageResource: 63 return ResourceLoadPriorityLow; 64#if ENABLE(LINK_PREFETCH) 65 case CachedResource::LinkPrefetch: 66 return ResourceLoadPriorityVeryLow; 67#endif 68 } 69 ASSERT_NOT_REACHED(); 70 return ResourceLoadPriorityLow; 71} 72 73#ifndef NDEBUG 74static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource"); 75#endif 76 77CachedResource::CachedResource(const String& url, Type type) 78 : m_url(url) 79 , m_request(0) 80 , m_loadPriority(defaultPriorityForResourceType(type)) 81 , m_responseTimestamp(currentTime()) 82 , m_lastDecodedAccessTime(0) 83 , m_encodedSize(0) 84 , m_decodedSize(0) 85 , m_accessCount(0) 86 , m_handleCount(0) 87 , m_preloadCount(0) 88 , m_preloadResult(PreloadNotReferenced) 89 , m_inLiveDecodedResourcesList(false) 90 , m_requestedFromNetworkingLayer(false) 91 , m_sendResourceLoadCallbacks(true) 92 , m_inCache(false) 93 , m_loading(false) 94 , m_type(type) 95 , m_status(Pending) 96 , m_deleted(false) 97#ifndef NDEBUG 98 , m_lruIndex(0) 99#endif 100 , m_nextInAllResourcesList(0) 101 , m_prevInAllResourcesList(0) 102 , m_nextInLiveResourcesList(0) 103 , m_prevInLiveResourcesList(0) 104 , m_owningCachedResourceLoader(0) 105 , m_resourceToRevalidate(0) 106 , m_proxyResource(0) 107{ 108#ifndef NDEBUG 109 cachedResourceLeakCounter.increment(); 110#endif 111} 112 113CachedResource::~CachedResource() 114{ 115 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this. 116 ASSERT(canDelete()); 117 ASSERT(!inCache()); 118 ASSERT(!m_deleted); 119 ASSERT(url().isNull() || memoryCache()->resourceForURL(KURL(ParsedURLString, url())) != this); 120 121 if (m_deleted) { 122 // FIXME: Remove when http://webkit.org/b/53045 is fixed. 123 CRASH(); 124 } 125 126 m_deleted = true; 127#ifndef NDEBUG 128 cachedResourceLeakCounter.decrement(); 129#endif 130 131 if (m_owningCachedResourceLoader) 132 m_owningCachedResourceLoader->removeCachedResource(this); 133} 134 135void CachedResource::load(CachedResourceLoader* cachedResourceLoader, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) 136{ 137 m_sendResourceLoadCallbacks = sendResourceLoadCallbacks; 138 cachedResourceLoader->load(this, incremental, securityCheck, sendResourceLoadCallbacks); 139 m_loading = true; 140} 141 142void CachedResource::data(PassRefPtr<SharedBuffer>, bool allDataReceived) 143{ 144 if (!allDataReceived) 145 return; 146 147 setLoading(false); 148 CachedResourceClientWalker w(m_clients); 149 while (CachedResourceClient* c = w.next()) 150 c->notifyFinished(this); 151} 152 153void CachedResource::finish() 154{ 155 m_status = Cached; 156} 157 158bool CachedResource::isExpired() const 159{ 160 if (m_response.isNull()) 161 return false; 162 163 return currentAge() > freshnessLifetime(); 164} 165 166double CachedResource::currentAge() const 167{ 168 // RFC2616 13.2.3 169 // No compensation for latency as that is not terribly important in practice 170 double dateValue = m_response.date(); 171 double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0; 172 double ageValue = m_response.age(); 173 double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge; 174 double residentTime = currentTime() - m_responseTimestamp; 175 return correctedReceivedAge + residentTime; 176} 177 178double CachedResource::freshnessLifetime() const 179{ 180 // Cache non-http resources liberally 181 if (!m_response.url().protocolInHTTPFamily()) 182 return std::numeric_limits<double>::max(); 183 184 // RFC2616 13.2.4 185 double maxAgeValue = m_response.cacheControlMaxAge(); 186 if (isfinite(maxAgeValue)) 187 return maxAgeValue; 188 double expiresValue = m_response.expires(); 189 double dateValue = m_response.date(); 190 double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp; 191 if (isfinite(expiresValue)) 192 return expiresValue - creationTime; 193 double lastModifiedValue = m_response.lastModified(); 194 if (isfinite(lastModifiedValue)) 195 return (creationTime - lastModifiedValue) * 0.1; 196 // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0. 197 return 0; 198} 199 200void CachedResource::setResponse(const ResourceResponse& response) 201{ 202 m_response = response; 203 m_responseTimestamp = currentTime(); 204} 205 206void CachedResource::setSerializedCachedMetadata(const char* data, size_t size) 207{ 208 // We only expect to receive cached metadata from the platform once. 209 // If this triggers, it indicates an efficiency problem which is most 210 // likely unexpected in code designed to improve performance. 211 ASSERT(!m_cachedMetadata); 212 213 m_cachedMetadata = CachedMetadata::deserialize(data, size); 214} 215 216void CachedResource::setCachedMetadata(unsigned dataTypeID, const char* data, size_t size) 217{ 218 // Currently, only one type of cached metadata per resource is supported. 219 // If the need arises for multiple types of metadata per resource this could 220 // be enhanced to store types of metadata in a map. 221 ASSERT(!m_cachedMetadata); 222 223 m_cachedMetadata = CachedMetadata::create(dataTypeID, data, size); 224 ResourceHandle::cacheMetadata(m_response, m_cachedMetadata->serialize()); 225} 226 227CachedMetadata* CachedResource::cachedMetadata(unsigned dataTypeID) const 228{ 229 if (!m_cachedMetadata || m_cachedMetadata->dataTypeID() != dataTypeID) 230 return 0; 231 return m_cachedMetadata.get(); 232} 233 234void CachedResource::setRequest(CachedResourceRequest* request) 235{ 236 if (request && !m_request) 237 m_status = Pending; 238 m_request = request; 239 if (canDelete() && !inCache()) 240 delete this; 241} 242 243void CachedResource::addClient(CachedResourceClient* client) 244{ 245 addClientToSet(client); 246 didAddClient(client); 247} 248 249void CachedResource::didAddClient(CachedResourceClient* c) 250{ 251 if (!isLoading()) 252 c->notifyFinished(this); 253} 254 255void CachedResource::addClientToSet(CachedResourceClient* client) 256{ 257 ASSERT(!isPurgeable()); 258 259 if (m_preloadResult == PreloadNotReferenced) { 260 if (isLoaded()) 261 m_preloadResult = PreloadReferencedWhileComplete; 262 else if (m_requestedFromNetworkingLayer) 263 m_preloadResult = PreloadReferencedWhileLoading; 264 else 265 m_preloadResult = PreloadReferenced; 266 } 267 if (!hasClients() && inCache()) 268 memoryCache()->addToLiveResourcesSize(this); 269 m_clients.add(client); 270} 271 272void CachedResource::removeClient(CachedResourceClient* client) 273{ 274 ASSERT(m_clients.contains(client)); 275 m_clients.remove(client); 276 277 if (canDelete() && !inCache()) 278 delete this; 279 else if (!hasClients() && inCache()) { 280 memoryCache()->removeFromLiveResourcesSize(this); 281 memoryCache()->removeFromLiveDecodedResourcesList(this); 282 allClientsRemoved(); 283 if (response().cacheControlContainsNoStore()) { 284 // RFC2616 14.9.2: 285 // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" 286 // "... History buffers MAY store such responses as part of their normal operation." 287 // We allow non-secure content to be reused in history, but we do not allow secure content to be reused. 288 if (protocolIs(url(), "https")) 289 memoryCache()->remove(this); 290 } else 291 memoryCache()->prune(); 292 } 293 // This object may be dead here. 294} 295 296void CachedResource::deleteIfPossible() 297{ 298 if (canDelete() && !inCache()) 299 delete this; 300} 301 302void CachedResource::setDecodedSize(unsigned size) 303{ 304 if (size == m_decodedSize) 305 return; 306 307 int delta = size - m_decodedSize; 308 309 // The object must now be moved to a different queue, since its size has been changed. 310 // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous 311 // queue. 312 if (inCache()) 313 memoryCache()->removeFromLRUList(this); 314 315 m_decodedSize = size; 316 317 if (inCache()) { 318 // Now insert into the new LRU list. 319 memoryCache()->insertInLRUList(this); 320 321 // Insert into or remove from the live decoded list if necessary. 322 // When inserting into the LiveDecodedResourcesList it is possible 323 // that the m_lastDecodedAccessTime is still zero or smaller than 324 // the m_lastDecodedAccessTime of the current list head. This is a 325 // violation of the invariant that the list is to be kept sorted 326 // by access time. The weakening of the invariant does not pose 327 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209 328 if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients()) 329 memoryCache()->insertInLiveDecodedResourcesList(this); 330 else if (!m_decodedSize && m_inLiveDecodedResourcesList) 331 memoryCache()->removeFromLiveDecodedResourcesList(this); 332 333 // Update the cache's size totals. 334 memoryCache()->adjustSize(hasClients(), delta); 335 } 336} 337 338void CachedResource::setEncodedSize(unsigned size) 339{ 340 if (size == m_encodedSize) 341 return; 342 343 // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert. 344 ASSERT(size == 0 || size >= m_encodedSize); 345 346 int delta = size - m_encodedSize; 347 348 // The object must now be moved to a different queue, since its size has been changed. 349 // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous 350 // queue. 351 if (inCache()) 352 memoryCache()->removeFromLRUList(this); 353 354 m_encodedSize = size; 355 356 if (inCache()) { 357 // Now insert into the new LRU list. 358 memoryCache()->insertInLRUList(this); 359 360 // Update the cache's size totals. 361 memoryCache()->adjustSize(hasClients(), delta); 362 } 363} 364 365void CachedResource::didAccessDecodedData(double timeStamp) 366{ 367 m_lastDecodedAccessTime = timeStamp; 368 369 if (inCache()) { 370 if (m_inLiveDecodedResourcesList) { 371 memoryCache()->removeFromLiveDecodedResourcesList(this); 372 memoryCache()->insertInLiveDecodedResourcesList(this); 373 } 374 memoryCache()->prune(); 375 } 376} 377 378void CachedResource::setResourceToRevalidate(CachedResource* resource) 379{ 380 ASSERT(resource); 381 ASSERT(!m_resourceToRevalidate); 382 ASSERT(resource != this); 383 ASSERT(m_handlesToRevalidate.isEmpty()); 384 ASSERT(resource->type() == type()); 385 386 LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource); 387 388 // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances. 389 // https://bugs.webkit.org/show_bug.cgi?id=28604. 390 // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate. 391 ASSERT(!resource->m_proxyResource); 392 393 resource->m_proxyResource = this; 394 m_resourceToRevalidate = resource; 395} 396 397void CachedResource::clearResourceToRevalidate() 398{ 399 ASSERT(m_resourceToRevalidate); 400 // 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. 401 if (m_resourceToRevalidate->m_proxyResource == this) { 402 m_resourceToRevalidate->m_proxyResource = 0; 403 m_resourceToRevalidate->deleteIfPossible(); 404 } 405 m_handlesToRevalidate.clear(); 406 m_resourceToRevalidate = 0; 407 deleteIfPossible(); 408} 409 410void CachedResource::switchClientsToRevalidatedResource() 411{ 412 ASSERT(m_resourceToRevalidate); 413 ASSERT(m_resourceToRevalidate->inCache()); 414 ASSERT(!inCache()); 415 416 LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate); 417 418 HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); 419 for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { 420 CachedResourceHandleBase* handle = *it; 421 handle->m_resource = m_resourceToRevalidate; 422 m_resourceToRevalidate->registerHandle(handle); 423 --m_handleCount; 424 } 425 ASSERT(!m_handleCount); 426 m_handlesToRevalidate.clear(); 427 428 Vector<CachedResourceClient*> clientsToMove; 429 HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end(); 430 for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) { 431 CachedResourceClient* client = it->first; 432 unsigned count = it->second; 433 while (count) { 434 clientsToMove.append(client); 435 --count; 436 } 437 } 438 // Equivalent of calling removeClient() for all clients 439 m_clients.clear(); 440 441 unsigned moveCount = clientsToMove.size(); 442 for (unsigned n = 0; n < moveCount; ++n) 443 m_resourceToRevalidate->addClientToSet(clientsToMove[n]); 444 for (unsigned n = 0; n < moveCount; ++n) { 445 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. 446 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n])) 447 m_resourceToRevalidate->didAddClient(clientsToMove[n]); 448 } 449} 450 451void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) 452{ 453 m_responseTimestamp = currentTime(); 454 455 DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-")); 456 // RFC2616 10.3.5 457 // Update cached headers from the 304 response 458 const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields(); 459 HTTPHeaderMap::const_iterator end = newHeaders.end(); 460 for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) { 461 // Don't allow 304 response to update content headers, these can't change but some servers send wrong values. 462 if (it->first.startsWith(contentHeaderPrefix, false)) 463 continue; 464 m_response.setHTTPHeaderField(it->first, it->second); 465 } 466} 467 468void CachedResource::registerHandle(CachedResourceHandleBase* h) 469{ 470 ++m_handleCount; 471 if (m_resourceToRevalidate) 472 m_handlesToRevalidate.add(h); 473} 474 475void CachedResource::unregisterHandle(CachedResourceHandleBase* h) 476{ 477 ASSERT(m_handleCount > 0); 478 --m_handleCount; 479 480 if (m_resourceToRevalidate) 481 m_handlesToRevalidate.remove(h); 482 483 if (!m_handleCount) 484 deleteIfPossible(); 485} 486 487bool CachedResource::canUseCacheValidator() const 488{ 489 if (m_loading || errorOccurred()) 490 return false; 491 492 if (m_response.cacheControlContainsNoStore()) 493 return false; 494 495 DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified")); 496 DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag")); 497 return !m_response.httpHeaderField(lastModifiedHeader).isEmpty() || !m_response.httpHeaderField(eTagHeader).isEmpty(); 498} 499 500bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const 501{ 502 ASSERT(cachePolicy == CachePolicyRevalidate || cachePolicy == CachePolicyCache || cachePolicy == CachePolicyVerify); 503 504 if (cachePolicy == CachePolicyRevalidate) 505 return true; 506 507 if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) { 508 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this); 509 return true; 510 } 511 512 if (cachePolicy == CachePolicyCache) { 513 if (m_response.cacheControlContainsMustRevalidate() && isExpired()) { 514 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this); 515 return true; 516 } 517 return false; 518 } 519 520 // CachePolicyVerify 521 if (isExpired()) { 522 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this); 523 return true; 524 } 525 526 return false; 527} 528 529bool CachedResource::isSafeToMakePurgeable() const 530{ 531 return !hasClients() && !m_proxyResource && !m_resourceToRevalidate; 532} 533 534bool CachedResource::makePurgeable(bool purgeable) 535{ 536 if (purgeable) { 537 ASSERT(isSafeToMakePurgeable()); 538 539 if (m_purgeableData) { 540 ASSERT(!m_data); 541 return true; 542 } 543 if (!m_data) 544 return false; 545 546 // Should not make buffer purgeable if it has refs other than this since we don't want two copies. 547 if (!m_data->hasOneRef()) 548 return false; 549 550 if (m_data->hasPurgeableBuffer()) { 551 m_purgeableData = m_data->releasePurgeableBuffer(); 552 } else { 553 m_purgeableData = PurgeableBuffer::create(m_data->data(), m_data->size()); 554 if (!m_purgeableData) 555 return false; 556 m_purgeableData->setPurgePriority(purgePriority()); 557 } 558 559 m_purgeableData->makePurgeable(true); 560 m_data.clear(); 561 return true; 562 } 563 564 if (!m_purgeableData) 565 return true; 566 ASSERT(!m_data); 567 ASSERT(!hasClients()); 568 569 if (!m_purgeableData->makePurgeable(false)) 570 return false; 571 572 m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release()); 573 return true; 574} 575 576bool CachedResource::isPurgeable() const 577{ 578 return m_purgeableData && m_purgeableData->isPurgeable(); 579} 580 581bool CachedResource::wasPurged() const 582{ 583 return m_purgeableData && m_purgeableData->wasPurged(); 584} 585 586unsigned CachedResource::overheadSize() const 587{ 588 return sizeof(CachedResource) + m_response.memoryUsage() + 576; 589 /* 590 576 = 192 + // average size of m_url 591 384; // average size of m_clients hash map 592 */ 593} 594 595void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority) 596{ 597 if (loadPriority == ResourceLoadPriorityUnresolved) 598 return; 599 m_loadPriority = loadPriority; 600} 601 602} 603