Home | History | Annotate | Download | only in history
      1 /*
      2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "PageCache.h"
     28 
     29 #include "Cache.h"
     30 #include "CachedPage.h"
     31 #include "FrameLoader.h"
     32 #include "HistoryItem.h"
     33 #include "Logging.h"
     34 #include "SystemTime.h"
     35 #include <wtf/CurrentTime.h>
     36 
     37 using namespace std;
     38 
     39 namespace WebCore {
     40 
     41 static const double autoreleaseInterval = 3;
     42 
     43 PageCache* pageCache()
     44 {
     45     static PageCache* staticPageCache = new PageCache;
     46     return staticPageCache;
     47 }
     48 
     49 PageCache::PageCache()
     50     : m_capacity(0)
     51     , m_size(0)
     52     , m_head(0)
     53     , m_tail(0)
     54     , m_autoreleaseTimer(this, &PageCache::releaseAutoreleasedPagesNowOrReschedule)
     55 {
     56 }
     57 
     58 void PageCache::setCapacity(int capacity)
     59 {
     60     ASSERT(capacity >= 0);
     61     m_capacity = max(capacity, 0);
     62 
     63     prune();
     64 }
     65 
     66 int PageCache::frameCount() const
     67 {
     68     int frameCount = 0;
     69     for (HistoryItem* current = m_head; current; current = current->m_next) {
     70         ++frameCount;
     71         ASSERT(current->m_cachedPage);
     72         frameCount += current->m_cachedPage ? current->m_cachedPage->cachedMainFrame()->descendantFrameCount() : 0;
     73     }
     74 
     75     return frameCount;
     76 }
     77 
     78 int PageCache::autoreleasedPageCount() const
     79 {
     80     return m_autoreleaseSet.size();
     81 }
     82 
     83 void PageCache::add(PassRefPtr<HistoryItem> prpItem, PassRefPtr<CachedPage> cachedPage)
     84 {
     85     ASSERT(prpItem);
     86     ASSERT(cachedPage);
     87 
     88     HistoryItem* item = prpItem.releaseRef(); // Balanced in remove().
     89 
     90     // Remove stale cache entry if necessary.
     91     if (item->m_cachedPage)
     92         remove(item);
     93 
     94     item->m_cachedPage = cachedPage;
     95     addToLRUList(item);
     96     ++m_size;
     97 
     98     prune();
     99 }
    100 
    101 void PageCache::remove(HistoryItem* item)
    102 {
    103     // Safely ignore attempts to remove items not in the cache.
    104     if (!item || !item->m_cachedPage)
    105         return;
    106 
    107     autorelease(item->m_cachedPage.release());
    108     removeFromLRUList(item);
    109     --m_size;
    110 
    111     item->deref(); // Balanced in add().
    112 }
    113 
    114 void PageCache::prune()
    115 {
    116     while (m_size > m_capacity) {
    117         ASSERT(m_tail && m_tail->m_cachedPage);
    118         remove(m_tail);
    119     }
    120 }
    121 
    122 void PageCache::addToLRUList(HistoryItem* item)
    123 {
    124     item->m_next = m_head;
    125     item->m_prev = 0;
    126 
    127     if (m_head) {
    128         ASSERT(m_tail);
    129         m_head->m_prev = item;
    130     } else {
    131         ASSERT(!m_tail);
    132         m_tail = item;
    133     }
    134 
    135     m_head = item;
    136 }
    137 
    138 void PageCache::removeFromLRUList(HistoryItem* item)
    139 {
    140     if (!item->m_next) {
    141         ASSERT(item == m_tail);
    142         m_tail = item->m_prev;
    143     } else {
    144         ASSERT(item != m_tail);
    145         item->m_next->m_prev = item->m_prev;
    146     }
    147 
    148     if (!item->m_prev) {
    149         ASSERT(item == m_head);
    150         m_head = item->m_next;
    151     } else {
    152         ASSERT(item != m_head);
    153         item->m_prev->m_next = item->m_next;
    154     }
    155 }
    156 
    157 void PageCache::releaseAutoreleasedPagesNowOrReschedule(Timer<PageCache>* timer)
    158 {
    159     double loadDelta = currentTime() - FrameLoader::timeOfLastCompletedLoad();
    160     float userDelta = userIdleTime();
    161 
    162     // FIXME: <rdar://problem/5211190> This limit of 42 risks growing the page cache far beyond its nominal capacity.
    163     if ((userDelta < 0.5 || loadDelta < 1.25) && m_autoreleaseSet.size() < 42) {
    164         LOG(PageCache, "WebCorePageCache: Postponing releaseAutoreleasedPagesNowOrReschedule() - %f since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
    165         timer->startOneShot(autoreleaseInterval);
    166         return;
    167     }
    168 
    169     LOG(PageCache, "WebCorePageCache: Releasing page caches - %f seconds since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
    170     releaseAutoreleasedPagesNow();
    171 }
    172 
    173 void PageCache::releaseAutoreleasedPagesNow()
    174 {
    175     m_autoreleaseTimer.stop();
    176 
    177     // Postpone dead pruning until all our resources have gone dead.
    178     cache()->setPruneEnabled(false);
    179 
    180     CachedPageSet tmp;
    181     tmp.swap(m_autoreleaseSet);
    182 
    183     CachedPageSet::iterator end = tmp.end();
    184     for (CachedPageSet::iterator it = tmp.begin(); it != end; ++it)
    185         (*it)->destroy();
    186 
    187     // Now do the prune.
    188     cache()->setPruneEnabled(true);
    189     cache()->prune();
    190 }
    191 
    192 void PageCache::autorelease(PassRefPtr<CachedPage> page)
    193 {
    194     ASSERT(page);
    195     ASSERT(!m_autoreleaseSet.contains(page.get()));
    196     m_autoreleaseSet.add(page);
    197     if (!m_autoreleaseTimer.isActive())
    198         m_autoreleaseTimer.startOneShot(autoreleaseInterval);
    199 }
    200 
    201 } // namespace WebCore
    202