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