From cad810f21b803229eb11403f9209855525a25d57 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Fri, 6 May 2011 11:45:16 +0100 Subject: Merge WebKit at r75315: Initial merge by git. Change-Id: I570314b346ce101c935ed22a626b48c2af266b84 --- Source/WebCore/history/PageCache.cpp | 479 +++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 Source/WebCore/history/PageCache.cpp (limited to 'Source/WebCore/history/PageCache.cpp') diff --git a/Source/WebCore/history/PageCache.cpp b/Source/WebCore/history/PageCache.cpp new file mode 100644 index 0000000..7375a9c --- /dev/null +++ b/Source/WebCore/history/PageCache.cpp @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "PageCache.h" + +#include "ApplicationCacheHost.h" +#include "BackForwardController.h" +#include "MemoryCache.h" +#include "CachedPage.h" +#include "DOMWindow.h" +#include "DeviceMotionController.h" +#include "DeviceOrientationController.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "FrameLoaderStateMachine.h" +#include "HistoryItem.h" +#include "Logging.h" +#include "Page.h" +#include "Settings.h" +#include "SharedWorkerRepository.h" +#include "SystemTime.h" +#include +#include +#include + +using namespace std; + +namespace WebCore { + +static const double autoreleaseInterval = 3; + +#ifndef NDEBUG + +static String& pageCacheLogPrefix(int indentLevel) +{ + static int previousIndent = -1; + DEFINE_STATIC_LOCAL(String, prefix, ()); + + if (indentLevel != previousIndent) { + previousIndent = indentLevel; + prefix.truncate(0); + for (int i = 0; i < previousIndent; ++i) + prefix += " "; + } + + return prefix; +} + +static void pageCacheLog(const String& prefix, const String& message) +{ + LOG(PageCache, "%s%s", prefix.utf8().data(), message.utf8().data()); +} + +#define PCLOG(...) pageCacheLog(pageCacheLogPrefix(indentLevel), makeString(__VA_ARGS__)) + +static bool logCanCacheFrameDecision(Frame* frame, int indentLevel) +{ + // Only bother logging for frames that have actually loaded and have content. + if (frame->loader()->stateMachine()->creatingInitialEmptyDocument()) + return false; + KURL currentURL = frame->loader()->documentLoader() ? frame->loader()->documentLoader()->url() : KURL(); + if (currentURL.isEmpty()) + return false; + + PCLOG("+---"); + KURL newURL = frame->loader()->provisionalDocumentLoader() ? frame->loader()->provisionalDocumentLoader()->url() : KURL(); + if (!newURL.isEmpty()) + PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):"); + else + PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:"); + + bool cannotCache = false; + + do { + if (!frame->loader()->documentLoader()) { + PCLOG(" -There is no DocumentLoader object"); + cannotCache = true; + break; + } + if (!frame->loader()->documentLoader()->mainDocumentError().isNull()) { + PCLOG(" -Main document has an error"); + cannotCache = true; + } + if (frame->loader()->subframeLoader()->containsPlugins()) { + PCLOG(" -Frame contains plugins"); + cannotCache = true; + } + if (frame->loader()->url().protocolIs("https")) { + PCLOG(" -Frame is HTTPS"); + cannotCache = true; + } + if (frame->domWindow() && frame->domWindow()->hasEventListeners(eventNames().unloadEvent)) { + PCLOG(" -Frame has an unload event listener"); + cannotCache = true; + } +#if ENABLE(DATABASE) + if (frame->document()->hasOpenDatabases()) { + PCLOG(" -Frame has open database handles"); + cannotCache = true; + } +#endif +#if ENABLE(SHARED_WORKERS) + if (SharedWorkerRepository::hasSharedWorkers(frame->document())) { + PCLOG(" -Frame has associated SharedWorkers"); + cannotCache = true; + } +#endif + if (frame->document()->usingGeolocation()) { + PCLOG(" -Frame uses Geolocation"); + cannotCache = true; + } + if (!frame->loader()->history()->currentItem()) { + PCLOG(" -No current history item"); + cannotCache = true; + } + if (frame->loader()->quickRedirectComing()) { + PCLOG(" -Quick redirect is coming"); + cannotCache = true; + } + if (frame->loader()->documentLoader()->isLoadingInAPISense()) { + PCLOG(" -DocumentLoader is still loading in API sense"); + cannotCache = true; + } + if (frame->loader()->documentLoader()->isStopping()) { + PCLOG(" -DocumentLoader is in the middle of stopping"); + cannotCache = true; + } + if (!frame->document()->canSuspendActiveDOMObjects()) { + PCLOG(" -The document cannot suspect its active DOM Objects"); + cannotCache = true; + } +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (!frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()) { + PCLOG(" -The DocumentLoader uses an application cache"); + cannotCache = true; + } +#endif + if (!frame->loader()->client()->canCachePage()) { + PCLOG(" -The client says this frame cannot be cached"); + cannotCache = true; + } + } while (false); + + for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + if (!logCanCacheFrameDecision(child, indentLevel + 1)) + cannotCache = true; + + PCLOG(cannotCache ? " Frame CANNOT be cached" : " Frame CAN be cached"); + PCLOG("+---"); + + return !cannotCache; +} + +static void logCanCachePageDecision(Page* page) +{ + // Only bother logging for main frames that have actually loaded and have content. + if (page->mainFrame()->loader()->stateMachine()->creatingInitialEmptyDocument()) + return; + KURL currentURL = page->mainFrame()->loader()->documentLoader() ? page->mainFrame()->loader()->documentLoader()->url() : KURL(); + if (currentURL.isEmpty()) + return; + + int indentLevel = 0; + PCLOG("--------\n Determining if page can be cached:"); + + bool cannotCache = !logCanCacheFrameDecision(page->mainFrame(), 1); + + FrameLoadType loadType = page->mainFrame()->loader()->loadType(); + if (!page->backForward()->isActive()) { + PCLOG(" -The back/forward list is disabled or has 0 capacity"); + cannotCache = true; + } + if (!page->settings()->usesPageCache()) { + PCLOG(" -Page settings says b/f cache disabled"); + cannotCache = true; + } +#if ENABLE(DEVICE_ORIENTATION) + if (page->deviceMotionController() && page->deviceMotionController()->isActive()) { + PCLOG(" -Page is using DeviceMotion"); + cannotCache = true; + } + if (page->deviceOrientationController() && page->deviceOrientationController()->isActive()) { + PCLOG(" -Page is using DeviceOrientation"); + cannotCache = true; + } +#endif + if (loadType == FrameLoadTypeReload) { + PCLOG(" -Load type is: Reload"); + cannotCache = true; + } + if (loadType == FrameLoadTypeReloadFromOrigin) { + PCLOG(" -Load type is: Reload from origin"); + cannotCache = true; + } + if (loadType == FrameLoadTypeSame) { + PCLOG(" -Load type is: Same"); + cannotCache = true; + } + + PCLOG(cannotCache ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------"); +} + +#endif + +PageCache* pageCache() +{ + static PageCache* staticPageCache = new PageCache; + return staticPageCache; +} + +PageCache::PageCache() + : m_capacity(0) + , m_size(0) + , m_head(0) + , m_tail(0) + , m_autoreleaseTimer(this, &PageCache::releaseAutoreleasedPagesNowOrReschedule) +{ +} + +bool PageCache::canCachePageContainingThisFrame(Frame* frame) +{ + for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) { + if (!canCachePageContainingThisFrame(child)) + return false; + } + + return frame->loader()->documentLoader() + && frame->loader()->documentLoader()->mainDocumentError().isNull() + // FIXME: If we ever change this so that frames with plug-ins will be cached, + // we need to make sure that we don't cache frames that have outstanding NPObjects + // (objects created by the plug-in). Since there is no way to pause/resume a Netscape plug-in, + // they would need to be destroyed and then recreated, and there is no way that we can recreate + // the right NPObjects. See for more information. + && !frame->loader()->subframeLoader()->containsPlugins() + && !frame->loader()->url().protocolIs("https") + && (!frame->domWindow() || !frame->domWindow()->hasEventListeners(eventNames().unloadEvent)) +#if ENABLE(DATABASE) + && !frame->document()->hasOpenDatabases() +#endif +#if ENABLE(SHARED_WORKERS) + && !SharedWorkerRepository::hasSharedWorkers(frame->document()) +#endif + && !frame->document()->usingGeolocation() + && frame->loader()->history()->currentItem() + && !frame->loader()->quickRedirectComing() + && !frame->loader()->documentLoader()->isLoadingInAPISense() + && !frame->loader()->documentLoader()->isStopping() + && frame->document()->canSuspendActiveDOMObjects() +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + // FIXME: We should investigating caching frames that have an associated + // application cache. tracks that work. + && frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache() +#endif +#if ENABLE(WML) + && !frame->document()->containsWMLContent() + && !frame->document()->isWMLDocument() +#endif + && frame->loader()->client()->canCachePage(); +} + +bool PageCache::canCache(Page* page) +{ + if (!page) + return false; + +#ifndef NDEBUG + logCanCachePageDecision(page); +#endif + + // Cache the page, if possible. + // Don't write to the cache if in the middle of a redirect, since we will want to + // store the final page we end up on. + // No point writing to the cache on a reload or loadSame, since we will just write + // over it again when we leave that page. + // FIXME: - We should work out the complexities of caching pages with frames as they + // are the most interesting pages on the web, and often those that would benefit the most from caching! + FrameLoadType loadType = page->mainFrame()->loader()->loadType(); + + return canCachePageContainingThisFrame(page->mainFrame()) + && page->backForward()->isActive() + && page->settings()->usesPageCache() +#if ENABLE(DEVICE_ORIENTATION) + && !(page->deviceMotionController() && page->deviceMotionController()->isActive()) + && !(page->deviceOrientationController() && page->deviceOrientationController()->isActive()) +#endif + && loadType != FrameLoadTypeReload + && loadType != FrameLoadTypeReloadFromOrigin + && loadType != FrameLoadTypeSame; +} + +void PageCache::setCapacity(int capacity) +{ + ASSERT(capacity >= 0); + m_capacity = max(capacity, 0); + + prune(); +} + +int PageCache::frameCount() const +{ + int frameCount = 0; + for (HistoryItem* current = m_head; current; current = current->m_next) { + ++frameCount; + ASSERT(current->m_cachedPage); + frameCount += current->m_cachedPage ? current->m_cachedPage->cachedMainFrame()->descendantFrameCount() : 0; + } + + return frameCount; +} + +int PageCache::autoreleasedPageCount() const +{ + return m_autoreleaseSet.size(); +} + +void PageCache::add(PassRefPtr prpItem, Page* page) +{ + ASSERT(prpItem); + ASSERT(page); + ASSERT(canCache(page)); + + HistoryItem* item = prpItem.releaseRef(); // Balanced in remove(). + + // Remove stale cache entry if necessary. + if (item->m_cachedPage) + remove(item); + + item->m_cachedPage = CachedPage::create(page); + addToLRUList(item); + ++m_size; + + prune(); +} + +CachedPage* PageCache::get(HistoryItem* item) +{ + if (!item) + return 0; + + if (CachedPage* cachedPage = item->m_cachedPage.get()) { + // FIXME: 1800 should not be hardcoded, it should come from + // WebKitBackForwardCacheExpirationIntervalKey in WebKit. + // Or we should remove WebKitBackForwardCacheExpirationIntervalKey. + if (currentTime() - cachedPage->timeStamp() <= 1800) + return cachedPage; + + LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data()); + pageCache()->remove(item); + } + return 0; +} + +void PageCache::remove(HistoryItem* item) +{ + // Safely ignore attempts to remove items not in the cache. + if (!item || !item->m_cachedPage) + return; + + autorelease(item->m_cachedPage.release()); + removeFromLRUList(item); + --m_size; + + item->deref(); // Balanced in add(). +} + +void PageCache::prune() +{ + while (m_size > m_capacity) { + ASSERT(m_tail && m_tail->m_cachedPage); + remove(m_tail); + } +} + +void PageCache::addToLRUList(HistoryItem* item) +{ + item->m_next = m_head; + item->m_prev = 0; + + if (m_head) { + ASSERT(m_tail); + m_head->m_prev = item; + } else { + ASSERT(!m_tail); + m_tail = item; + } + + m_head = item; +} + +void PageCache::removeFromLRUList(HistoryItem* item) +{ + if (!item->m_next) { + ASSERT(item == m_tail); + m_tail = item->m_prev; + } else { + ASSERT(item != m_tail); + item->m_next->m_prev = item->m_prev; + } + + if (!item->m_prev) { + ASSERT(item == m_head); + m_head = item->m_next; + } else { + ASSERT(item != m_head); + item->m_prev->m_next = item->m_next; + } +} + +void PageCache::releaseAutoreleasedPagesNowOrReschedule(Timer* timer) +{ + double loadDelta = currentTime() - FrameLoader::timeOfLastCompletedLoad(); + float userDelta = userIdleTime(); + + // FIXME: This limit of 42 risks growing the page cache far beyond its nominal capacity. + if ((userDelta < 0.5 || loadDelta < 1.25) && m_autoreleaseSet.size() < 42) { + LOG(PageCache, "WebCorePageCache: Postponing releaseAutoreleasedPagesNowOrReschedule() - %f since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size()); + timer->startOneShot(autoreleaseInterval); + return; + } + + LOG(PageCache, "WebCorePageCache: Releasing page caches - %f seconds since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size()); + releaseAutoreleasedPagesNow(); +} + +void PageCache::releaseAutoreleasedPagesNow() +{ + m_autoreleaseTimer.stop(); + + // Postpone dead pruning until all our resources have gone dead. + cache()->setPruneEnabled(false); + + CachedPageSet tmp; + tmp.swap(m_autoreleaseSet); + + CachedPageSet::iterator end = tmp.end(); + for (CachedPageSet::iterator it = tmp.begin(); it != end; ++it) + (*it)->destroy(); + + // Now do the prune. + cache()->setPruneEnabled(true); + cache()->prune(); +} + +void PageCache::autorelease(PassRefPtr page) +{ + ASSERT(page); + ASSERT(!m_autoreleaseSet.contains(page.get())); + m_autoreleaseSet.add(page); + if (!m_autoreleaseTimer.isActive()) + m_autoreleaseTimer.startOneShot(autoreleaseInterval); +} + +} // namespace WebCore -- cgit v1.1