summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/history/PageCache.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/history/PageCache.cpp
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/history/PageCache.cpp')
-rw-r--r--Source/WebCore/history/PageCache.cpp479
1 files changed, 479 insertions, 0 deletions
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 <wtf/CurrentTime.h>
+#include <wtf/text/CString.h>
+#include <wtf/text/StringConcatenate.h>
+
+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 <rdar://problem/5197041> 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. <rdar://problem/5917899> 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: <rdar://problem/4886592> - 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<HistoryItem> 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<PageCache>* timer)
+{
+ double loadDelta = currentTime() - FrameLoader::timeOfLastCompletedLoad();
+ float userDelta = userIdleTime();
+
+ // FIXME: <rdar://problem/5211190> 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<CachedPage> page)
+{
+ ASSERT(page);
+ ASSERT(!m_autoreleaseSet.contains(page.get()));
+ m_autoreleaseSet.add(page);
+ if (!m_autoreleaseTimer.isActive())
+ m_autoreleaseTimer.startOneShot(autoreleaseInterval);
+}
+
+} // namespace WebCore