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