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 37using namespace std; 38 39namespace WebCore { 40 41static const double autoreleaseInterval = 3; 42 43PageCache* pageCache() 44{ 45 static PageCache* staticPageCache = new PageCache; 46 return staticPageCache; 47} 48 49PageCache::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 58void PageCache::setCapacity(int capacity) 59{ 60 ASSERT(capacity >= 0); 61 m_capacity = max(capacity, 0); 62 63 prune(); 64} 65 66int 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 78int PageCache::autoreleasedPageCount() const 79{ 80 return m_autoreleaseSet.size(); 81} 82 83void 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 101void 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 114void PageCache::prune() 115{ 116 while (m_size > m_capacity) { 117 ASSERT(m_tail && m_tail->m_cachedPage); 118 remove(m_tail); 119 } 120} 121 122void 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 138void 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 157void 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 173void 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 192void 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