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, 2009, 2010, 2011 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 "CachedResourceClient.h" 28#include "CachedResourceClientWalker.h" 29#include "CachedResourceHandle.h" 30#include "CachedResourceLoader.h" 31#include "CrossOriginAccessControl.h" 32#include "Document.h" 33#include "DocumentLoader.h" 34#include "FrameLoader.h" 35#include "FrameLoaderClient.h" 36#include "HTTPHeaderNames.h" 37#include "InspectorInstrumentation.h" 38#include "URL.h" 39#include "LoaderStrategy.h" 40#include "Logging.h" 41#include "MemoryCache.h" 42#include "PlatformStrategies.h" 43#include "PurgeableBuffer.h" 44#include "ResourceBuffer.h" 45#include "ResourceHandle.h" 46#include "ResourceLoadScheduler.h" 47#include "SchemeRegistry.h" 48#include "SecurityOrigin.h" 49#include "SecurityPolicy.h" 50#include "SubresourceLoader.h" 51#include <wtf/CurrentTime.h> 52#include <wtf/MathExtras.h> 53#include <wtf/RefCountedLeakCounter.h> 54#include <wtf/StdLibExtras.h> 55#include <wtf/text/CString.h> 56#include <wtf/Vector.h> 57 58#if USE(QUICK_LOOK) 59#include "QuickLook.h" 60#endif 61 62using namespace WTF; 63 64namespace WebCore { 65 66// These response headers are not copied from a revalidated response to the 67// cached response headers. For compatibility, this list is based on Chromium's 68// net/http/http_response_headers.cc. 69const char* const headersToIgnoreAfterRevalidation[] = { 70 "allow", 71 "connection", 72 "etag", 73 "expires", 74 "keep-alive", 75 "last-modified" 76 "proxy-authenticate", 77 "proxy-connection", 78 "trailer", 79 "transfer-encoding", 80 "upgrade", 81 "www-authenticate", 82 "x-frame-options", 83 "x-xss-protection", 84}; 85 86// Some header prefixes mean "Don't copy this header from a 304 response.". 87// Rather than listing all the relevant headers, we can consolidate them into 88// this list, also grabbed from Chromium's net/http/http_response_headers.cc. 89const char* const headerPrefixesToIgnoreAfterRevalidation[] = { 90 "content-", 91 "x-content-", 92 "x-webkit-" 93}; 94 95static inline bool shouldUpdateHeaderAfterRevalidation(const String& header) 96{ 97 for (size_t i = 0; i < WTF_ARRAY_LENGTH(headersToIgnoreAfterRevalidation); i++) { 98 if (equalIgnoringCase(header, headersToIgnoreAfterRevalidation[i])) 99 return false; 100 } 101 for (size_t i = 0; i < WTF_ARRAY_LENGTH(headerPrefixesToIgnoreAfterRevalidation); i++) { 102 if (header.startsWith(headerPrefixesToIgnoreAfterRevalidation[i], false)) 103 return false; 104 } 105 return true; 106} 107 108static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type) 109{ 110 switch (type) { 111 case CachedResource::MainResource: 112 return ResourceLoadPriorityVeryHigh; 113 case CachedResource::CSSStyleSheet: 114 return ResourceLoadPriorityHigh; 115 case CachedResource::Script: 116 case CachedResource::FontResource: 117 case CachedResource::RawResource: 118 return ResourceLoadPriorityMedium; 119 case CachedResource::ImageResource: 120 return ResourceLoadPriorityLow; 121#if ENABLE(XSLT) 122 case CachedResource::XSLStyleSheet: 123 return ResourceLoadPriorityHigh; 124#endif 125 case CachedResource::SVGDocumentResource: 126 return ResourceLoadPriorityLow; 127#if ENABLE(LINK_PREFETCH) 128 case CachedResource::LinkPrefetch: 129 return ResourceLoadPriorityVeryLow; 130 case CachedResource::LinkSubresource: 131 return ResourceLoadPriorityVeryLow; 132#endif 133#if ENABLE(VIDEO_TRACK) 134 case CachedResource::TextTrackResource: 135 return ResourceLoadPriorityLow; 136#endif 137 } 138 ASSERT_NOT_REACHED(); 139 return ResourceLoadPriorityLow; 140} 141 142static std::chrono::milliseconds deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type) 143{ 144 if (type == CachedResource::Script) 145 return std::chrono::milliseconds { 0 }; 146 147 return memoryCache()->deadDecodedDataDeletionInterval(); 148} 149 150DEFINE_DEBUG_ONLY_GLOBAL(RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource")); 151 152CachedResource::CachedResource(const ResourceRequest& request, Type type, SessionID sessionID) 153 : m_resourceRequest(request) 154 , m_sessionID(sessionID) 155 , m_loadPriority(defaultPriorityForResourceType(type)) 156 , m_responseTimestamp(currentTime()) 157 , m_decodedDataDeletionTimer(this, &CachedResource::decodedDataDeletionTimerFired, deadDecodedDataDeletionIntervalForResourceType(type)) 158 , m_lastDecodedAccessTime(0) 159 , m_loadFinishTime(0) 160 , m_encodedSize(0) 161 , m_decodedSize(0) 162 , m_accessCount(0) 163 , m_handleCount(0) 164 , m_preloadCount(0) 165 , m_preloadResult(PreloadNotReferenced) 166 , m_inLiveDecodedResourcesList(false) 167 , m_requestedFromNetworkingLayer(false) 168 , m_inCache(false) 169 , m_loading(false) 170 , m_switchingClientsToRevalidatedResource(false) 171 , m_type(type) 172 , m_status(Pending) 173#ifndef NDEBUG 174 , m_deleted(false) 175 , m_lruIndex(0) 176#endif 177 , m_nextInAllResourcesList(0) 178 , m_prevInAllResourcesList(0) 179 , m_nextInLiveResourcesList(0) 180 , m_prevInLiveResourcesList(0) 181 , m_owningCachedResourceLoader(0) 182 , m_resourceToRevalidate(0) 183 , m_proxyResource(0) 184{ 185 ASSERT(m_type == unsigned(type)); // m_type is a bitfield, so this tests careless updates of the enum. 186 ASSERT(sessionID.isValid()); 187#ifndef NDEBUG 188 cachedResourceLeakCounter.increment(); 189#endif 190 191 if (!m_resourceRequest.url().hasFragmentIdentifier()) 192 return; 193 URL urlForCache = MemoryCache::removeFragmentIdentifierIfNeeded(m_resourceRequest.url()); 194 if (urlForCache.hasFragmentIdentifier()) 195 return; 196 m_fragmentIdentifierForRequest = m_resourceRequest.url().fragmentIdentifier(); 197 m_resourceRequest.setURL(urlForCache); 198} 199 200CachedResource::~CachedResource() 201{ 202 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this. 203 ASSERT(canDelete()); 204 ASSERT(!inCache()); 205 ASSERT(!m_deleted); 206 ASSERT(url().isNull() || memoryCache()->resourceForRequest(resourceRequest(), sessionID()) != this); 207 208#ifndef NDEBUG 209 m_deleted = true; 210 cachedResourceLeakCounter.decrement(); 211#endif 212 213 if (m_owningCachedResourceLoader) 214 m_owningCachedResourceLoader->removeCachedResource(this); 215} 216 217void CachedResource::failBeforeStarting() 218{ 219 // FIXME: What if resources in other frames were waiting for this revalidation? 220 LOG(ResourceLoading, "Cannot start loading '%s'", url().string().latin1().data()); 221 if (m_resourceToRevalidate) 222 memoryCache()->revalidationFailed(this); 223 error(CachedResource::LoadError); 224} 225 226void CachedResource::addAdditionalRequestHeaders(CachedResourceLoader* cachedResourceLoader) 227{ 228 // Note: We skip the Content-Security-Policy check here because we check 229 // the Content-Security-Policy at the CachedResourceLoader layer so we can 230 // handle different resource types differently. 231 232 FrameLoader& frameLoader = cachedResourceLoader->frame()->loader(); 233 String outgoingReferrer; 234 String outgoingOrigin; 235 if (m_resourceRequest.httpReferrer().isNull()) { 236 outgoingReferrer = frameLoader.outgoingReferrer(); 237 outgoingOrigin = frameLoader.outgoingOrigin(); 238 } else { 239 outgoingReferrer = m_resourceRequest.httpReferrer(); 240 outgoingOrigin = SecurityOrigin::createFromString(outgoingReferrer)->toString(); 241 } 242 243 outgoingReferrer = SecurityPolicy::generateReferrerHeader(cachedResourceLoader->document()->referrerPolicy(), m_resourceRequest.url(), outgoingReferrer); 244 if (outgoingReferrer.isEmpty()) 245 m_resourceRequest.clearHTTPReferrer(); 246 else if (!m_resourceRequest.httpReferrer()) 247 m_resourceRequest.setHTTPReferrer(outgoingReferrer); 248 FrameLoader::addHTTPOriginIfNeeded(m_resourceRequest, outgoingOrigin); 249 250 frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest); 251} 252 253void CachedResource::load(CachedResourceLoader* cachedResourceLoader, const ResourceLoaderOptions& options) 254{ 255 if (!cachedResourceLoader->frame()) { 256 failBeforeStarting(); 257 return; 258 } 259 260 FrameLoader& frameLoader = cachedResourceLoader->frame()->loader(); 261 if (options.securityCheck() == DoSecurityCheck && (frameLoader.state() == FrameStateProvisional || !frameLoader.activeDocumentLoader() || frameLoader.activeDocumentLoader()->isStopping())) { 262 failBeforeStarting(); 263 return; 264 } 265 266 m_options = options; 267 m_loading = true; 268 269#if USE(QUICK_LOOK) 270 if (!m_resourceRequest.isNull() && m_resourceRequest.url().protocolIs(QLPreviewProtocol())) { 271 // When QuickLook is invoked to convert a document, it returns a unique URL in the 272 // NSURLReponse for the main document. To make safeQLURLForDocumentURLAndResourceURL() 273 // work, we need to use the QL URL not the original URL. 274 const URL& documentURL = cachedResourceLoader->frame() ? cachedResourceLoader->frame()->loader().documentLoader()->response().url() : cachedResourceLoader->document()->url(); 275 m_resourceRequest.setURL(safeQLURLForDocumentURLAndResourceURL(documentURL, url())); 276 } 277#endif 278 279 if (!accept().isEmpty()) 280 m_resourceRequest.setHTTPAccept(accept()); 281 282 if (isCacheValidator()) { 283 CachedResource* resourceToRevalidate = m_resourceToRevalidate; 284 ASSERT(resourceToRevalidate->canUseCacheValidator()); 285 ASSERT(resourceToRevalidate->isLoaded()); 286 const String& lastModified = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::LastModified); 287 const String& eTag = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::ETag); 288 if (!lastModified.isEmpty() || !eTag.isEmpty()) { 289 ASSERT(cachedResourceLoader->cachePolicy(type()) != CachePolicyReload); 290 if (cachedResourceLoader->cachePolicy(type()) == CachePolicyRevalidate) 291 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0"); 292 if (!lastModified.isEmpty()) 293 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified); 294 if (!eTag.isEmpty()) 295 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag); 296 } 297 } 298 299#if ENABLE(LINK_PREFETCH) 300 if (type() == CachedResource::LinkPrefetch || type() == CachedResource::LinkSubresource) 301 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Purpose, "prefetch"); 302#endif 303 m_resourceRequest.setPriority(loadPriority()); 304 305 if (type() != MainResource) 306 addAdditionalRequestHeaders(cachedResourceLoader); 307 308 // FIXME: It's unfortunate that the cache layer and below get to know anything about fragment identifiers. 309 // We should look into removing the expectation of that knowledge from the platform network stacks. 310 ResourceRequest request(m_resourceRequest); 311 if (!m_fragmentIdentifierForRequest.isNull()) { 312 URL url = request.url(); 313 url.setFragmentIdentifier(m_fragmentIdentifierForRequest); 314 request.setURL(url); 315 m_fragmentIdentifierForRequest = String(); 316 } 317 318 m_loader = platformStrategies()->loaderStrategy()->resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->frame(), this, request, request.priority(), options); 319 if (!m_loader) { 320 failBeforeStarting(); 321 return; 322 } 323 324 m_status = Pending; 325} 326 327void CachedResource::checkNotify() 328{ 329 if (isLoading() || stillNeedsLoad()) 330 return; 331 332 CachedResourceClientWalker<CachedResourceClient> w(m_clients); 333 while (CachedResourceClient* c = w.next()) 334 c->notifyFinished(this); 335} 336 337void CachedResource::addDataBuffer(ResourceBuffer*) 338{ 339 ASSERT(m_options.dataBufferingPolicy() == BufferData); 340} 341 342void CachedResource::addData(const char*, unsigned) 343{ 344 ASSERT(m_options.dataBufferingPolicy() == DoNotBufferData); 345} 346 347void CachedResource::finishLoading(ResourceBuffer*) 348{ 349 setLoading(false); 350 checkNotify(); 351} 352 353void CachedResource::error(CachedResource::Status status) 354{ 355 setStatus(status); 356 ASSERT(errorOccurred()); 357 m_data.clear(); 358 359 setLoading(false); 360 checkNotify(); 361} 362 363void CachedResource::cancelLoad() 364{ 365 if (!isLoading() && !stillNeedsLoad()) 366 return; 367 368 setStatus(LoadError); 369 setLoading(false); 370 checkNotify(); 371} 372 373void CachedResource::finish() 374{ 375 if (!errorOccurred()) 376 m_status = Cached; 377} 378 379bool CachedResource::passesAccessControlCheck(SecurityOrigin* securityOrigin) 380{ 381 String errorDescription; 382 return WebCore::passesAccessControlCheck(m_response, resourceRequest().allowCookies() ? AllowStoredCredentials : DoNotAllowStoredCredentials, securityOrigin, errorDescription); 383} 384 385bool CachedResource::isExpired() const 386{ 387 if (m_response.isNull()) 388 return false; 389 390 return currentAge() > freshnessLifetime(); 391} 392 393double CachedResource::currentAge() const 394{ 395 // RFC2616 13.2.3 396 // No compensation for latency as that is not terribly important in practice 397 double dateValue = m_response.date(); 398 double apparentAge = std::isfinite(dateValue) ? std::max(0., m_responseTimestamp - dateValue) : 0; 399 double ageValue = m_response.age(); 400 double correctedReceivedAge = std::isfinite(ageValue) ? std::max(apparentAge, ageValue) : apparentAge; 401 double residentTime = currentTime() - m_responseTimestamp; 402 return correctedReceivedAge + residentTime; 403} 404 405double CachedResource::freshnessLifetime() const 406{ 407 if (!m_response.url().protocolIsInHTTPFamily()) { 408 // Don't cache non-HTTP main resources since we can't check for freshness. 409 // FIXME: We should not cache subresources either, but when we tried this 410 // it caused performance and flakiness issues in our test infrastructure. 411 if (m_type == MainResource && !SchemeRegistry::shouldCacheResponsesFromURLSchemeIndefinitely(m_response.url().protocol())) 412 return 0; 413 414 return std::numeric_limits<double>::max(); 415 } 416 417 // RFC2616 13.2.4 418 double maxAgeValue = m_response.cacheControlMaxAge(); 419 if (std::isfinite(maxAgeValue)) 420 return maxAgeValue; 421 double expiresValue = m_response.expires(); 422 double dateValue = m_response.date(); 423 double creationTime = std::isfinite(dateValue) ? dateValue : m_responseTimestamp; 424 if (std::isfinite(expiresValue)) 425 return expiresValue - creationTime; 426 double lastModifiedValue = m_response.lastModified(); 427 if (std::isfinite(lastModifiedValue)) 428 return (creationTime - lastModifiedValue) * 0.1; 429 // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0. 430 return 0; 431} 432 433void CachedResource::responseReceived(const ResourceResponse& response) 434{ 435 setResponse(response); 436 m_responseTimestamp = currentTime(); 437 String encoding = response.textEncodingName(); 438 if (!encoding.isNull()) 439 setEncoding(encoding); 440} 441 442void CachedResource::clearLoader() 443{ 444 ASSERT(m_loader); 445 m_loader = 0; 446} 447 448void CachedResource::addClient(CachedResourceClient* client) 449{ 450 if (addClientToSet(client)) 451 didAddClient(client); 452} 453 454void CachedResource::didAddClient(CachedResourceClient* c) 455{ 456 if (m_decodedDataDeletionTimer.isActive()) 457 m_decodedDataDeletionTimer.stop(); 458 459 if (m_clientsAwaitingCallback.contains(c)) { 460 m_clients.add(c); 461 m_clientsAwaitingCallback.remove(c); 462 } 463 if (!isLoading() && !stillNeedsLoad()) 464 c->notifyFinished(this); 465} 466 467bool CachedResource::addClientToSet(CachedResourceClient* client) 468{ 469 ASSERT(!isPurgeable()); 470 471 if (m_preloadResult == PreloadNotReferenced) { 472 if (isLoaded()) 473 m_preloadResult = PreloadReferencedWhileComplete; 474 else if (m_requestedFromNetworkingLayer) 475 m_preloadResult = PreloadReferencedWhileLoading; 476 else 477 m_preloadResult = PreloadReferenced; 478 } 479 if (!hasClients() && inCache()) 480 memoryCache()->addToLiveResourcesSize(this); 481 482 if ((m_type == RawResource || m_type == MainResource) && !m_response.isNull() && !m_proxyResource) { 483 // Certain resources (especially XHRs and main resources) do crazy things if an asynchronous load returns 484 // synchronously (e.g., scripts may not have set all the state they need to handle the load). 485 // Therefore, rather than immediately sending callbacks on a cache hit like other CachedResources, 486 // we schedule the callbacks and ensure we never finish synchronously. 487 ASSERT(!m_clientsAwaitingCallback.contains(client)); 488 m_clientsAwaitingCallback.add(client, CachedResourceCallback::schedule(this, client)); 489 return false; 490 } 491 492 m_clients.add(client); 493 return true; 494} 495 496void CachedResource::removeClient(CachedResourceClient* client) 497{ 498 OwnPtr<CachedResourceCallback> callback = m_clientsAwaitingCallback.take(client); 499 if (callback) { 500 ASSERT(!m_clients.contains(client)); 501 callback->cancel(); 502 callback.clear(); 503 } else { 504 ASSERT(m_clients.contains(client)); 505 m_clients.remove(client); 506 didRemoveClient(client); 507 } 508 509 bool deleted = deleteIfPossible(); 510 if (!deleted && !hasClients()) { 511 if (inCache()) { 512 memoryCache()->removeFromLiveResourcesSize(this); 513 memoryCache()->removeFromLiveDecodedResourcesList(this); 514 } 515 if (!m_switchingClientsToRevalidatedResource) 516 allClientsRemoved(); 517 destroyDecodedDataIfNeeded(); 518 if (response().cacheControlContainsNoStore() && url().protocolIs("https")) { 519 // RFC2616 14.9.2: 520 // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" 521 // "... History buffers MAY store such responses as part of their normal operation." 522 // We allow non-secure content to be reused in history, but we do not allow secure content to be reused. 523 memoryCache()->remove(this); 524 } 525 memoryCache()->prune(); 526 } 527 // This object may be dead here. 528} 529 530void CachedResource::destroyDecodedDataIfNeeded() 531{ 532 if (!m_decodedSize) 533 return; 534 if (!memoryCache()->deadDecodedDataDeletionInterval().count()) 535 return; 536 m_decodedDataDeletionTimer.restart(); 537} 538 539void CachedResource::decodedDataDeletionTimerFired() 540{ 541 destroyDecodedData(); 542} 543 544bool CachedResource::deleteIfPossible() 545{ 546 if (canDelete() && !inCache()) { 547 InspectorInstrumentation::willDestroyCachedResource(this); 548 delete this; 549 return true; 550 } 551 return false; 552} 553 554void CachedResource::setDecodedSize(unsigned size) 555{ 556 if (size == m_decodedSize) 557 return; 558 559 int delta = size - m_decodedSize; 560 561 // The object must now be moved to a different queue, since its size has been changed. 562 // We have to remove explicitly before updating m_decodedSize, so that we find the correct previous 563 // queue. 564 if (inCache()) 565 memoryCache()->removeFromLRUList(this); 566 567 m_decodedSize = size; 568 569 if (inCache()) { 570 // Now insert into the new LRU list. 571 memoryCache()->insertInLRUList(this); 572 573 // Insert into or remove from the live decoded list if necessary. 574 // When inserting into the LiveDecodedResourcesList it is possible 575 // that the m_lastDecodedAccessTime is still zero or smaller than 576 // the m_lastDecodedAccessTime of the current list head. This is a 577 // violation of the invariant that the list is to be kept sorted 578 // by access time. The weakening of the invariant does not pose 579 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209 580 if (m_decodedSize && !m_inLiveDecodedResourcesList && hasClients()) 581 memoryCache()->insertInLiveDecodedResourcesList(this); 582 else if (!m_decodedSize && m_inLiveDecodedResourcesList) 583 memoryCache()->removeFromLiveDecodedResourcesList(this); 584 585 // Update the cache's size totals. 586 memoryCache()->adjustSize(hasClients(), delta); 587 } 588} 589 590void CachedResource::setEncodedSize(unsigned size) 591{ 592 if (size == m_encodedSize) 593 return; 594 595 // The size cannot ever shrink (unless it is being nulled out because of an error). If it ever does, assert. 596 ASSERT(size == 0 || size >= m_encodedSize); 597 598 int delta = size - m_encodedSize; 599 600 // The object must now be moved to a different queue, since its size has been changed. 601 // We have to remove explicitly before updating m_encodedSize, so that we find the correct previous 602 // queue. 603 if (inCache()) 604 memoryCache()->removeFromLRUList(this); 605 606 m_encodedSize = size; 607 608 if (inCache()) { 609 // Now insert into the new LRU list. 610 memoryCache()->insertInLRUList(this); 611 612 // Update the cache's size totals. 613 memoryCache()->adjustSize(hasClients(), delta); 614 } 615} 616 617void CachedResource::didAccessDecodedData(double timeStamp) 618{ 619 m_lastDecodedAccessTime = timeStamp; 620 621 if (inCache()) { 622 if (m_inLiveDecodedResourcesList) { 623 memoryCache()->removeFromLiveDecodedResourcesList(this); 624 memoryCache()->insertInLiveDecodedResourcesList(this); 625 } 626 memoryCache()->prune(); 627 } 628} 629 630void CachedResource::setResourceToRevalidate(CachedResource* resource) 631{ 632 ASSERT(resource); 633 ASSERT(!m_resourceToRevalidate); 634 ASSERT(resource != this); 635 ASSERT(m_handlesToRevalidate.isEmpty()); 636 ASSERT(resource->type() == type()); 637 638 LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource); 639 640 // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances. 641 // https://bugs.webkit.org/show_bug.cgi?id=28604. 642 // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate. 643 ASSERT(!resource->m_proxyResource); 644 645 resource->m_proxyResource = this; 646 m_resourceToRevalidate = resource; 647} 648 649void CachedResource::clearResourceToRevalidate() 650{ 651 ASSERT(m_resourceToRevalidate); 652 if (m_switchingClientsToRevalidatedResource) 653 return; 654 655 // 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. 656 if (m_resourceToRevalidate->m_proxyResource == this) { 657 m_resourceToRevalidate->m_proxyResource = 0; 658 m_resourceToRevalidate->deleteIfPossible(); 659 } 660 m_handlesToRevalidate.clear(); 661 m_resourceToRevalidate = 0; 662 deleteIfPossible(); 663} 664 665void CachedResource::switchClientsToRevalidatedResource() 666{ 667 ASSERT(m_resourceToRevalidate); 668 ASSERT(m_resourceToRevalidate->inCache()); 669 ASSERT(!inCache()); 670 671 LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate); 672 673 m_switchingClientsToRevalidatedResource = true; 674 HashSet<CachedResourceHandleBase*>::iterator end = m_handlesToRevalidate.end(); 675 for (HashSet<CachedResourceHandleBase*>::iterator it = m_handlesToRevalidate.begin(); it != end; ++it) { 676 CachedResourceHandleBase* handle = *it; 677 handle->m_resource = m_resourceToRevalidate; 678 m_resourceToRevalidate->registerHandle(handle); 679 --m_handleCount; 680 } 681 ASSERT(!m_handleCount); 682 m_handlesToRevalidate.clear(); 683 684 Vector<CachedResourceClient*> clientsToMove; 685 HashCountedSet<CachedResourceClient*>::iterator end2 = m_clients.end(); 686 for (HashCountedSet<CachedResourceClient*>::iterator it = m_clients.begin(); it != end2; ++it) { 687 CachedResourceClient* client = it->key; 688 unsigned count = it->value; 689 while (count) { 690 clientsToMove.append(client); 691 --count; 692 } 693 } 694 695 unsigned moveCount = clientsToMove.size(); 696 for (unsigned n = 0; n < moveCount; ++n) 697 removeClient(clientsToMove[n]); 698 ASSERT(m_clients.isEmpty()); 699 700 for (unsigned n = 0; n < moveCount; ++n) 701 m_resourceToRevalidate->addClientToSet(clientsToMove[n]); 702 for (unsigned n = 0; n < moveCount; ++n) { 703 // Calling didAddClient may do anything, including trying to cancel revalidation. 704 // Assert that it didn't succeed. 705 ASSERT(m_resourceToRevalidate); 706 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. 707 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n])) 708 m_resourceToRevalidate->didAddClient(clientsToMove[n]); 709 } 710 m_switchingClientsToRevalidatedResource = false; 711} 712 713void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) 714{ 715 m_responseTimestamp = currentTime(); 716 717 // RFC2616 10.3.5 718 // Update cached headers from the 304 response 719 for (const auto& header : validatingResponse.httpHeaderFields()) { 720 // Entity headers should not be sent by servers when generating a 304 721 // response; misconfigured servers send them anyway. We shouldn't allow 722 // such headers to update the original request. We'll base this on the 723 // list defined by RFC2616 7.1, with a few additions for extension headers 724 // we care about. 725 if (!shouldUpdateHeaderAfterRevalidation(header.key)) 726 continue; 727 m_response.setHTTPHeaderField(header.key, header.value); 728 } 729} 730 731void CachedResource::registerHandle(CachedResourceHandleBase* h) 732{ 733 ++m_handleCount; 734 if (m_resourceToRevalidate) 735 m_handlesToRevalidate.add(h); 736} 737 738void CachedResource::unregisterHandle(CachedResourceHandleBase* h) 739{ 740 ASSERT(m_handleCount > 0); 741 --m_handleCount; 742 743 if (m_resourceToRevalidate) 744 m_handlesToRevalidate.remove(h); 745 746 if (!m_handleCount) 747 deleteIfPossible(); 748} 749 750bool CachedResource::canUseCacheValidator() const 751{ 752 if (m_loading || errorOccurred()) 753 return false; 754 755 if (m_response.cacheControlContainsNoStore()) 756 return false; 757 return m_response.hasCacheValidatorFields(); 758} 759 760bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const 761{ 762 ASSERT(cachePolicy == CachePolicyRevalidate || cachePolicy == CachePolicyCache || cachePolicy == CachePolicyVerify); 763 764 if (cachePolicy == CachePolicyRevalidate) 765 return true; 766 767 if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) { 768 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this); 769 return true; 770 } 771 772 if (cachePolicy == CachePolicyCache) { 773 if (m_response.cacheControlContainsMustRevalidate() && isExpired()) { 774 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this); 775 return true; 776 } 777 return false; 778 } 779 780 // CachePolicyVerify 781 if (isExpired()) { 782 LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this); 783 return true; 784 } 785 786 return false; 787} 788 789bool CachedResource::isSafeToMakePurgeable() const 790{ 791#if ENABLE(DISK_IMAGE_CACHE) 792 // It does not make sense to have a resource in the disk image cache 793 // (memory mapped on disk) and purgeable (in memory). So do not allow 794 // disk image cached resources to be purgeable. 795 if (isUsingDiskImageCache()) 796 return false; 797#endif 798 799 return !hasClients() && !m_proxyResource && !m_resourceToRevalidate; 800} 801 802bool CachedResource::makePurgeable(bool purgeable) 803{ 804 if (purgeable) { 805 ASSERT(isSafeToMakePurgeable()); 806 807 if (m_purgeableData) { 808 ASSERT(!m_data); 809 return true; 810 } 811 if (!m_data) 812 return false; 813 814 m_data->createPurgeableBuffer(); 815 if (!m_data->hasPurgeableBuffer()) 816 return false; 817 818 m_purgeableData = m_data->releasePurgeableBuffer(); 819 m_purgeableData->setPurgePriority(purgePriority()); 820 m_purgeableData->makePurgeable(true); 821 m_data.clear(); 822 return true; 823 } 824 825 if (!m_purgeableData) 826 return true; 827 ASSERT(!m_data); 828 ASSERT(!hasClients()); 829 830 if (!m_purgeableData->makePurgeable(false)) 831 return false; 832 833 m_data = ResourceBuffer::adoptSharedBuffer(SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release())); 834 return true; 835} 836 837bool CachedResource::isPurgeable() const 838{ 839 return m_purgeableData && m_purgeableData->isPurgeable(); 840} 841 842bool CachedResource::wasPurged() const 843{ 844 return m_purgeableData && m_purgeableData->wasPurged(); 845} 846 847unsigned CachedResource::overheadSize() const 848{ 849 static const int kAverageClientsHashMapSize = 384; 850 return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2; 851} 852 853void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority) 854{ 855 if (loadPriority == ResourceLoadPriorityUnresolved) 856 loadPriority = defaultPriorityForResourceType(type()); 857 if (loadPriority == m_loadPriority) 858 return; 859 m_loadPriority = loadPriority; 860 if (m_loader) 861 m_loader->didChangePriority(loadPriority); 862} 863 864CachedResource::CachedResourceCallback::CachedResourceCallback(CachedResource* resource, CachedResourceClient* client) 865 : m_resource(resource) 866 , m_client(client) 867 , m_callbackTimer(this, &CachedResourceCallback::timerFired) 868{ 869 m_callbackTimer.startOneShot(0); 870} 871 872void CachedResource::CachedResourceCallback::cancel() 873{ 874 if (m_callbackTimer.isActive()) 875 m_callbackTimer.stop(); 876} 877 878void CachedResource::CachedResourceCallback::timerFired(Timer<CachedResourceCallback>&) 879{ 880 m_resource->didAddClient(m_client); 881} 882 883#if ENABLE(DISK_IMAGE_CACHE) 884bool CachedResource::isUsingDiskImageCache() const 885{ 886 return m_data && m_data->isUsingDiskImageCache(); 887} 888#endif 889 890#if USE(FOUNDATION) 891void CachedResource::tryReplaceEncodedData(PassRefPtr<SharedBuffer> newBuffer) 892{ 893 if (!m_data) 894 return; 895 896 if (!mayTryReplaceEncodedData()) 897 return; 898 899 // We have to do the memcmp because we can't tell if the replacement file backed data is for the 900 // same resource or if we made a second request with the same URL which gave us a different 901 // resource. We have seen this happen for cached POST resources. 902 if (m_data->size() != newBuffer->size() || memcmp(m_data->data(), newBuffer->data(), m_data->size())) 903 return; 904 905 m_data->tryReplaceSharedBufferContents(newBuffer.get()); 906} 907#endif 908 909} 910