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 "Cache.h" 28 #include "CachedResourceHandle.h" 29 #include "DocLoader.h" 30 #include "Frame.h" 31 #include "FrameLoader.h" 32 #include "KURL.h" 33 #include "PurgeableBuffer.h" 34 #include "Request.h" 35 #include <wtf/CurrentTime.h> 36 #include <wtf/MathExtras.h> 37 #include <wtf/RefCountedLeakCounter.h> 38 #include <wtf/StdLibExtras.h> 39 #include <wtf/Vector.h> 40 41 using namespace WTF; 42 43 namespace WebCore { 44 45 #ifndef NDEBUG 46 static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource"); 47 #endif 48 49 CachedResource::CachedResource(const String& url, Type type) 50 : m_url(url) 51 , m_responseTimestamp(currentTime()) 52 , m_lastDecodedAccessTime(0) 53 , m_sendResourceLoadCallbacks(true) 54 , m_preloadCount(0) 55 , m_preloadResult(PreloadNotReferenced) 56 , m_requestedFromNetworkingLayer(false) 57 , m_inCache(false) 58 , m_loading(false) 59 , m_docLoader(0) 60 , m_handleCount(0) 61 , m_resourceToRevalidate(0) 62 , m_proxyResource(0) 63 { 64 #ifndef NDEBUG 65 cachedResourceLeakCounter.increment(); 66 #endif 67 68 m_type = type; 69 m_status = Pending; 70 m_encodedSize = 0; 71 m_decodedSize = 0; 72 m_request = 0; 73 74 m_accessCount = 0; 75 m_inLiveDecodedResourcesList = false; 76 77 m_nextInAllResourcesList = 0; 78 m_prevInAllResourcesList = 0; 79 80 m_nextInLiveResourcesList = 0; 81 m_prevInLiveResourcesList = 0; 82 83 #ifndef NDEBUG 84 m_deleted = false; 85 m_lruIndex = 0; 86 #endif 87 m_errorOccurred = false; 88 } 89 90 CachedResource::~CachedResource() 91 { 92 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this. 93 ASSERT(canDelete()); 94 ASSERT(!inCache()); 95 ASSERT(!m_deleted); 96 ASSERT(url().isNull() || cache()->resourceForURL(url()) != this); 97 #ifndef NDEBUG 98 m_deleted = true; 99 cachedResourceLeakCounter.decrement(); 100 #endif 101 102 if (m_docLoader) 103 m_docLoader->removeCachedResource(this); 104 } 105 106 void CachedResource::load(DocLoader* docLoader, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) 107 { 108 m_sendResourceLoadCallbacks = sendResourceLoadCallbacks; 109 cache()->loader()->load(docLoader, this, incremental, securityCheck, sendResourceLoadCallbacks); 110 m_loading = true; 111 } 112 113 void CachedResource::finish() 114 { 115 m_status = Cached; 116 } 117 118 bool CachedResource::isExpired() const 119 { 120 if (m_response.isNull()) 121 return false; 122 123 return currentAge() > freshnessLifetime(); 124 } 125 126 double CachedResource::currentAge() const 127 { 128 // RFC2616 13.2.3 129 // No compensation for latency as that is not terribly important in practice 130 double dateValue = m_response.date(); 131 double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0; 132 double ageValue = m_response.age(); 133 double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge; 134 double residentTime = currentTime() - m_responseTimestamp; 135 return correctedReceivedAge + residentTime; 136 } 137 138 double CachedResource::freshnessLifetime() const 139 { 140 // Cache non-http resources liberally 141 if (!m_response.url().protocolInHTTPFamily()) 142 return std::numeric_limits<double>::max(); 143 144 // RFC2616 13.2.4 145 double maxAgeValue = m_response.cacheControlMaxAge(); 146 if (isfinite(maxAgeValue)) 147 return maxAgeValue; 148 double expiresValue = m_response.expires(); 149 double dateValue = m_response.date(); 150 double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp; 151 if (isfinite(expiresValue)) 152 return expiresValue - creationTime; 153 double lastModifiedValue = m_response.lastModified(); 154 if (isfinite(lastModifiedValue)) 155 return (creationTime - lastModifiedValue) * 0.1; 156 // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0. 157 return 0; 158 } 159 160 void CachedResource::setResponse(const ResourceResponse& response) 161 { 162 m_response = response; 163 m_responseTimestamp = currentTime(); 164 } 165 166 void CachedResource::setRequest(Request* request) 167 { 168 if (request && !m_request) 169 m_status = Pending; 170 m_request = request; 171 if (canDelete() && !inCache()) 172 delete this; 173 } 174 175 void CachedResource::addClient(CachedResourceClient* client) 176 { 177 addClientToSet(client); 178 didAddClient(client); 179 } 180 181 void CachedResource::addClientToSet(CachedResourceClient* client) 182 { 183 ASSERT(!isPurgeable()); 184 185 if (m_preloadResult == PreloadNotReferenced) { 186 if (isLoaded()) 187 m_preloadResult = PreloadReferencedWhileComplete; 188 else if (m_requestedFromNetworkingLayer) 189 m_preloadResult = PreloadReferencedWhileLoading; 190 else 191 m_preloadResult = PreloadReferenced; 192 } 193 if (!hasClients() && inCache()) 194 cache()->addToLiveResourcesSize(this); 195 m_clients.add(client); 196 } 197 198 void CachedResource::removeClient(CachedResourceClient* client) 199 { 200 ASSERT(m_clients.contains(client)); 201 m_clients.remove(client); 202 203 if (canDelete() && !inCache()) 204 delete this; 205 else if (!hasClients() && inCache()) { 206 cache()->removeFromLiveResourcesSize(this); 207 cache()->removeFromLiveDecodedResourcesList(this); 208 allClientsRemoved(); 209 if (response().cacheControlContainsNoStore()) { 210 // RFC2616 14.9.2: 211 // "no-store: ...MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" 212 cache()->remove(this); 213 } else 214 cache()->prune(); 215 } 216 // This object may be dead here. 217 } 218 219 void CachedResource::deleteIfPossible() 220 { 221 if (canDelete() && !inCache()) 222 delete this; 223 } 224 225 void CachedResource::setDecodedSize(unsigned size) 226 { 227 if (size == m_decodedSize) 228 return; 229 230 int delta = size - m_decodedSize; 231 232 // The object must now be moved to a different queue, since its size has been changed. 233 // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous 234 // queue. 235 if (inCache()) 236 cache()->removeFromLRUList(this); 237 238 m_decodedSize = size; 239 240 if (inCache()) { 241 // Now insert into the new LRU list. 242 cache()->insertInLRUList(this); 243 244 // Insert into or remove from the live decoded list if necessary. 245 // When inserting into the LiveDecodedResourcesList it is possible 246 // that the m_lastDecodedAccessTime is still zero or smaller than 247 // the m_lastDecodedAccessTime of the current list head. This is a 248 // violation of the invariant that the list is to be kept sorted 249 // by access time. The weakening of the invariant does not pose 250 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209 251 if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients()) 252 cache()->insertInLiveDecodedResourcesList(this); 253 else if (!m_decodedSize && m_inLiveDecodedResourcesList) 254 cache()->removeFromLiveDecodedResourcesList(this); 255 256 // Update the cache's size totals. 257 cache()->adjustSize(hasClients(), delta); 258 } 259 } 260 261 void CachedResource::setEncodedSize(unsigned size) 262 { 263 if (size == m_encodedSize) 264 return; 265 266 // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert. 267 ASSERT(size == 0 || size >= m_encodedSize); 268 269 int delta = size - m_encodedSize; 270 271 // The object must now be moved to a different queue, since its size has been changed. 272 // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous 273 // queue. 274 if (inCache()) 275 cache()->removeFromLRUList(this); 276 277 m_encodedSize = size; 278 279 if (inCache()) { 280 // Now insert into the new LRU list. 281 cache()->insertInLRUList(this); 282 283 // Update the cache's size totals. 284 cache()->adjustSize(hasClients(), delta); 285 } 286 } 287 288 void CachedResource::didAccessDecodedData(double timeStamp) 289 { 290 m_lastDecodedAccessTime = timeStamp; 291 292 if (inCache()) { 293 if (m_inLiveDecodedResourcesList) { 294 cache()->removeFromLiveDecodedResourcesList(this); 295 cache()->insertInLiveDecodedResourcesList(this); 296 } 297 cache()->prune(); 298 } 299 } 300 301 void CachedResource::setResourceToRevalidate(CachedResource* resource) 302 { 303 ASSERT(resource); 304 ASSERT(!m_resourceToRevalidate); 305 ASSERT(resource != this); 306 ASSERT(m_handlesToRevalidate.isEmpty()); 307 ASSERT(resource->type() == type()); 308 309 // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances. 310 // https://bugs.webkit.org/show_bug.cgi?id=28604. 311 // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate. 312 ASSERT(!resource->m_proxyResource); 313 314 resource->m_proxyResource = this; 315 m_resourceToRevalidate = resource; 316 } 317 318 void CachedResource::clearResourceToRevalidate() 319 { 320 ASSERT(m_resourceToRevalidate); 321 // 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. 322 if (m_resourceToRevalidate->m_proxyResource == this) { 323 m_resourceToRevalidate->m_proxyResource = 0; 324 m_resourceToRevalidate->deleteIfPossible(); 325 } 326 m_handlesToRevalidate.clear(); 327 m_resourceToRevalidate = 0; 328 deleteIfPossible(); 329 } 330 331 void CachedResource::switchClientsToRevalidatedResource() 332 { 333 ASSERT(m_resourceToRevalidate); 334 ASSERT(m_resourceToRevalidate->inCache()); 335 ASSERT(!inCache()); 336 337 HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); 338 for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { 339 CachedResourceHandleBase* handle = *it; 340 handle->m_resource = m_resourceToRevalidate; 341 m_resourceToRevalidate->registerHandle(handle); 342 --m_handleCount; 343 } 344 ASSERT(!m_handleCount); 345 m_handlesToRevalidate.clear(); 346 347 Vector<CachedResourceClient*> clientsToMove; 348 HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end(); 349 for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) { 350 CachedResourceClient* client = it->first; 351 unsigned count = it->second; 352 while (count) { 353 clientsToMove.append(client); 354 --count; 355 } 356 } 357 // Equivalent of calling removeClient() for all clients 358 m_clients.clear(); 359 360 unsigned moveCount = clientsToMove.size(); 361 for (unsigned n = 0; n < moveCount; ++n) 362 m_resourceToRevalidate->addClientToSet(clientsToMove[n]); 363 for (unsigned n = 0; n < moveCount; ++n) { 364 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. 365 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n])) 366 m_resourceToRevalidate->didAddClient(clientsToMove[n]); 367 } 368 } 369 370 void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) 371 { 372 m_responseTimestamp = currentTime(); 373 374 DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-")); 375 // RFC2616 10.3.5 376 // Update cached headers from the 304 response 377 const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields(); 378 HTTPHeaderMap::const_iterator end = newHeaders.end(); 379 for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) { 380 // Don't allow 304 response to update content headers, these can't change but some servers send wrong values. 381 if (it->first.startsWith(contentHeaderPrefix, false)) 382 continue; 383 m_response.setHTTPHeaderField(it->first, it->second); 384 } 385 } 386 387 bool CachedResource::canUseCacheValidator() const 388 { 389 if (m_loading || m_errorOccurred) 390 return false; 391 392 if (m_response.cacheControlContainsNoStore()) 393 return false; 394 395 DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified")); 396 DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag")); 397 return !m_response.httpHeaderField(lastModifiedHeader).isEmpty() || !m_response.httpHeaderField(eTagHeader).isEmpty(); 398 } 399 400 bool CachedResource::mustRevalidate(CachePolicy cachePolicy) const 401 { 402 if (m_errorOccurred) 403 return true; 404 405 if (m_loading) 406 return false; 407 408 if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) 409 return true; 410 411 if (cachePolicy == CachePolicyCache) 412 return m_response.cacheControlContainsMustRevalidate() && isExpired(); 413 414 return isExpired(); 415 } 416 417 bool CachedResource::isSafeToMakePurgeable() const 418 { 419 return !hasClients() && !m_proxyResource && !m_resourceToRevalidate; 420 } 421 422 bool CachedResource::makePurgeable(bool purgeable) 423 { 424 if (purgeable) { 425 ASSERT(isSafeToMakePurgeable()); 426 427 if (m_purgeableData) { 428 ASSERT(!m_data); 429 return true; 430 } 431 if (!m_data) 432 return false; 433 434 // Should not make buffer purgeable if it has refs other than this since we don't want two copies. 435 if (!m_data->hasOneRef()) 436 return false; 437 438 // Purgeable buffers are allocated in multiples of the page size (4KB in common CPUs) so it does not make sense for very small buffers. 439 const size_t purgeableThreshold = 4 * 4096; 440 if (m_data->size() < purgeableThreshold) 441 return false; 442 443 if (m_data->hasPurgeableBuffer()) { 444 m_purgeableData.set(m_data->releasePurgeableBuffer()); 445 } else { 446 m_purgeableData.set(PurgeableBuffer::create(m_data->data(), m_data->size())); 447 if (!m_purgeableData) 448 return false; 449 } 450 451 m_purgeableData->makePurgeable(true); 452 m_data.clear(); 453 return true; 454 } 455 456 if (!m_purgeableData) 457 return true; 458 ASSERT(!m_data); 459 ASSERT(!hasClients()); 460 461 if (!m_purgeableData->makePurgeable(false)) 462 return false; 463 464 m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release()); 465 return true; 466 } 467 468 bool CachedResource::isPurgeable() const 469 { 470 return m_purgeableData && m_purgeableData->isPurgeable(); 471 } 472 473 bool CachedResource::wasPurged() const 474 { 475 return m_purgeableData && m_purgeableData->wasPurged(); 476 } 477 478 unsigned CachedResource::overheadSize() const 479 { 480 return sizeof(CachedResource) + m_response.memoryUsage() + 576; 481 /* 482 576 = 192 + // average size of m_url 483 384; // average size of m_clients hash map 484 */ 485 } 486 487 } 488