2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "PageCache.h"
29 #include "ApplicationCacheHost.h"
30 #include "BackForwardController.h"
31 #include "MemoryCache.h"
32 #include "CachedPage.h"
33 #include "DOMWindow.h"
34 #include "DeviceMotionController.h"
35 #include "DeviceOrientationController.h"
37 #include "DocumentLoader.h"
39 #include "FrameLoader.h"
40 #include "FrameLoaderClient.h"
41 #include "FrameLoaderStateMachine.h"
42 #include "HistoryItem.h"
46 #include "SharedWorkerRepository.h"
47 #include "SystemTime.h"
48 #include <wtf/CurrentTime.h>
49 #include <wtf/text/CString.h>
50 #include <wtf/text/StringConcatenate.h>
56 static const double autoreleaseInterval = 3;
60 static String& pageCacheLogPrefix(int indentLevel)
62 static int previousIndent = -1;
63 DEFINE_STATIC_LOCAL(String, prefix, ());
65 if (indentLevel != previousIndent) {
66 previousIndent = indentLevel;
68 for (int i = 0; i < previousIndent; ++i)
75 static void pageCacheLog(const String& prefix, const String& message)
77 LOG(PageCache, "%s%s", prefix.utf8().data(), message.utf8().data());
80 #define PCLOG(...) pageCacheLog(pageCacheLogPrefix(indentLevel), makeString(__VA_ARGS__))
82 static bool logCanCacheFrameDecision(Frame* frame, int indentLevel)
84 // Only bother logging for frames that have actually loaded and have content.
85 if (frame->loader()->stateMachine()->creatingInitialEmptyDocument())
87 KURL currentURL = frame->loader()->documentLoader() ? frame->loader()->documentLoader()->url() : KURL();
88 if (currentURL.isEmpty())
92 KURL newURL = frame->loader()->provisionalDocumentLoader() ? frame->loader()->provisionalDocumentLoader()->url() : KURL();
93 if (!newURL.isEmpty())
94 PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
96 PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
98 bool cannotCache = false;
101 if (!frame->loader()->documentLoader()) {
102 PCLOG(" -There is no DocumentLoader object");
106 if (!frame->loader()->documentLoader()->mainDocumentError().isNull()) {
107 PCLOG(" -Main document has an error");
110 if (frame->loader()->subframeLoader()->containsPlugins()) {
111 PCLOG(" -Frame contains plugins");
114 if (frame->document()->url().protocolIs("https")) {
115 PCLOG(" -Frame is HTTPS");
118 if (frame->domWindow() && frame->domWindow()->hasEventListeners(eventNames().unloadEvent)) {
119 PCLOG(" -Frame has an unload event listener");
123 if (frame->document()->hasOpenDatabases()) {
124 PCLOG(" -Frame has open database handles");
128 #if ENABLE(SHARED_WORKERS)
129 if (SharedWorkerRepository::hasSharedWorkers(frame->document())) {
130 PCLOG(" -Frame has associated SharedWorkers");
134 if (frame->document()->usingGeolocation()) {
135 PCLOG(" -Frame uses Geolocation");
138 if (!frame->loader()->history()->currentItem()) {
139 PCLOG(" -No current history item");
142 if (frame->loader()->quickRedirectComing()) {
143 PCLOG(" -Quick redirect is coming");
146 if (frame->loader()->documentLoader()->isLoadingInAPISense()) {
147 PCLOG(" -DocumentLoader is still loading in API sense");
150 if (frame->loader()->documentLoader()->isStopping()) {
151 PCLOG(" -DocumentLoader is in the middle of stopping");
154 if (!frame->document()->canSuspendActiveDOMObjects()) {
155 PCLOG(" -The document cannot suspect its active DOM Objects");
158 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
159 if (!frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
160 PCLOG(" -The DocumentLoader uses an application cache");
164 if (!frame->loader()->client()->canCachePage()) {
165 PCLOG(" -The client says this frame cannot be cached");
170 for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
171 if (!logCanCacheFrameDecision(child, indentLevel + 1))
174 PCLOG(cannotCache ? " Frame CANNOT be cached" : " Frame CAN be cached");
180 static void logCanCachePageDecision(Page* page)
182 // Only bother logging for main frames that have actually loaded and have content.
183 if (page->mainFrame()->loader()->stateMachine()->creatingInitialEmptyDocument())
185 KURL currentURL = page->mainFrame()->loader()->documentLoader() ? page->mainFrame()->loader()->documentLoader()->url() : KURL();
186 if (currentURL.isEmpty())
190 PCLOG("--------\n Determining if page can be cached:");
192 bool cannotCache = !logCanCacheFrameDecision(page->mainFrame(), 1);
194 FrameLoadType loadType = page->mainFrame()->loader()->loadType();
195 if (!page->backForward()->isActive()) {
196 PCLOG(" -The back/forward list is disabled or has 0 capacity");
199 if (!page->settings()->usesPageCache()) {
200 PCLOG(" -Page settings says b/f cache disabled");
203 #if ENABLE(DEVICE_ORIENTATION)
204 if (page->deviceMotionController() && page->deviceMotionController()->isActive()) {
205 PCLOG(" -Page is using DeviceMotion");
208 if (page->deviceOrientationController() && page->deviceOrientationController()->isActive()) {
209 PCLOG(" -Page is using DeviceOrientation");
213 if (loadType == FrameLoadTypeReload) {
214 PCLOG(" -Load type is: Reload");
217 if (loadType == FrameLoadTypeReloadFromOrigin) {
218 PCLOG(" -Load type is: Reload from origin");
221 if (loadType == FrameLoadTypeSame) {
222 PCLOG(" -Load type is: Same");
226 PCLOG(cannotCache ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------");
231 PageCache* pageCache()
233 static PageCache* staticPageCache = new PageCache;
234 return staticPageCache;
237 PageCache::PageCache()
242 , m_autoreleaseTimer(this, &PageCache::releaseAutoreleasedPagesNowOrReschedule)
246 bool PageCache::canCachePageContainingThisFrame(Frame* frame)
248 for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
249 if (!canCachePageContainingThisFrame(child))
253 return frame->loader()->documentLoader()
254 && frame->loader()->documentLoader()->mainDocumentError().isNull()
255 // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
256 && !(frame->loader()->documentLoader()->substituteData().isValid() && !frame->loader()->documentLoader()->substituteData().failingURL().isEmpty())
257 // FIXME: If we ever change this so that frames with plug-ins will be cached,
258 // we need to make sure that we don't cache frames that have outstanding NPObjects
259 // (objects created by the plug-in). Since there is no way to pause/resume a Netscape plug-in,
260 // they would need to be destroyed and then recreated, and there is no way that we can recreate
261 // the right NPObjects. See <rdar://problem/5197041> for more information.
262 && !frame->loader()->subframeLoader()->containsPlugins()
263 && !frame->document()->url().protocolIs("https")
264 && (!frame->domWindow() || !frame->domWindow()->hasEventListeners(eventNames().unloadEvent))
266 && !frame->document()->hasOpenDatabases()
268 #if ENABLE(SHARED_WORKERS)
269 && !SharedWorkerRepository::hasSharedWorkers(frame->document())
271 && !frame->document()->usingGeolocation()
272 && frame->loader()->history()->currentItem()
273 && !frame->loader()->quickRedirectComing()
274 && !frame->loader()->documentLoader()->isLoadingInAPISense()
275 && !frame->loader()->documentLoader()->isStopping()
276 && frame->document()->canSuspendActiveDOMObjects()
277 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
278 // FIXME: We should investigating caching frames that have an associated
279 // application cache. <rdar://problem/5917899> tracks that work.
280 && frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()
283 && !frame->document()->containsWMLContent()
284 && !frame->document()->isWMLDocument()
286 && frame->loader()->client()->canCachePage();
289 bool PageCache::canCache(Page* page)
295 logCanCachePageDecision(page);
298 // Cache the page, if possible.
299 // Don't write to the cache if in the middle of a redirect, since we will want to
300 // store the final page we end up on.
301 // No point writing to the cache on a reload or loadSame, since we will just write
302 // over it again when we leave that page.
303 // FIXME: <rdar://problem/4886592> - We should work out the complexities of caching pages with frames as they
304 // are the most interesting pages on the web, and often those that would benefit the most from caching!
305 FrameLoadType loadType = page->mainFrame()->loader()->loadType();
307 return canCachePageContainingThisFrame(page->mainFrame())
308 && page->backForward()->isActive()
309 && page->settings()->usesPageCache()
310 #if ENABLE(DEVICE_ORIENTATION)
311 && !(page->deviceMotionController() && page->deviceMotionController()->isActive())
312 && !(page->deviceOrientationController() && page->deviceOrientationController()->isActive())
314 && loadType != FrameLoadTypeReload
315 && loadType != FrameLoadTypeReloadFromOrigin
316 && loadType != FrameLoadTypeSame;
319 void PageCache::setCapacity(int capacity)
321 ASSERT(capacity >= 0);
322 m_capacity = max(capacity, 0);
327 int PageCache::frameCount() const
330 for (HistoryItem* current = m_head; current; current = current->m_next) {
332 ASSERT(current->m_cachedPage);
333 frameCount += current->m_cachedPage ? current->m_cachedPage->cachedMainFrame()->descendantFrameCount() : 0;
339 int PageCache::autoreleasedPageCount() const
341 return m_autoreleaseSet.size();
344 void PageCache::markPagesForVistedLinkStyleRecalc()
346 for (HistoryItem* current = m_head; current; current = current->m_next)
347 current->m_cachedPage->markForVistedLinkStyleRecalc();
350 void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page* page)
354 ASSERT(canCache(page));
356 HistoryItem* item = prpItem.releaseRef(); // Balanced in remove().
358 // Remove stale cache entry if necessary.
359 if (item->m_cachedPage)
362 item->m_cachedPage = CachedPage::create(page);
369 CachedPage* PageCache::get(HistoryItem* item)
374 if (CachedPage* cachedPage = item->m_cachedPage.get()) {
375 // FIXME: 1800 should not be hardcoded, it should come from
376 // WebKitBackForwardCacheExpirationIntervalKey in WebKit.
377 // Or we should remove WebKitBackForwardCacheExpirationIntervalKey.
378 if (currentTime() - cachedPage->timeStamp() <= 1800)
381 LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
382 pageCache()->remove(item);
387 void PageCache::remove(HistoryItem* item)
389 // Safely ignore attempts to remove items not in the cache.
390 if (!item || !item->m_cachedPage)
393 autorelease(item->m_cachedPage.release());
394 removeFromLRUList(item);
397 item->deref(); // Balanced in add().
400 void PageCache::prune()
402 while (m_size > m_capacity) {
403 ASSERT(m_tail && m_tail->m_cachedPage);
408 void PageCache::addToLRUList(HistoryItem* item)
410 item->m_next = m_head;
415 m_head->m_prev = item;
424 void PageCache::removeFromLRUList(HistoryItem* item)
427 ASSERT(item == m_tail);
428 m_tail = item->m_prev;
430 ASSERT(item != m_tail);
431 item->m_next->m_prev = item->m_prev;
435 ASSERT(item == m_head);
436 m_head = item->m_next;
438 ASSERT(item != m_head);
439 item->m_prev->m_next = item->m_next;
443 void PageCache::releaseAutoreleasedPagesNowOrReschedule(Timer<PageCache>* timer)
445 double loadDelta = currentTime() - FrameLoader::timeOfLastCompletedLoad();
446 float userDelta = userIdleTime();
448 // FIXME: <rdar://problem/5211190> This limit of 42 risks growing the page cache far beyond its nominal capacity.
449 if ((userDelta < 0.5 || loadDelta < 1.25) && m_autoreleaseSet.size() < 42) {
450 LOG(PageCache, "WebCorePageCache: Postponing releaseAutoreleasedPagesNowOrReschedule() - %f since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
451 timer->startOneShot(autoreleaseInterval);
455 LOG(PageCache, "WebCorePageCache: Releasing page caches - %f seconds since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
456 releaseAutoreleasedPagesNow();
459 void PageCache::releaseAutoreleasedPagesNow()
461 m_autoreleaseTimer.stop();
463 // Postpone dead pruning until all our resources have gone dead.
464 memoryCache()->setPruneEnabled(false);
467 tmp.swap(m_autoreleaseSet);
469 CachedPageSet::iterator end = tmp.end();
470 for (CachedPageSet::iterator it = tmp.begin(); it != end; ++it)
474 memoryCache()->setPruneEnabled(true);
475 memoryCache()->prune();
478 void PageCache::autorelease(PassRefPtr<CachedPage> page)
481 ASSERT(!m_autoreleaseSet.contains(page.get()));
482 m_autoreleaseSet.add(page);
483 if (!m_autoreleaseTimer.isActive())
484 m_autoreleaseTimer.startOneShot(autoreleaseInterval);
487 } // namespace WebCore