Home | History | Annotate | Download | only in loader
      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