Commit d48109cb authored by japhet@chromium.org's avatar japhet@chromium.org
Browse files

2010-06-04 Nate Chapin <japhet@chromium.org>

        Reviewed by Adam Barth.

        Factor PageCache functionality out of FrameLoader and into
        PageCache itself.

        https://bugs.webkit.org/show_bug.cgi?id=39382

        Refactor only, so no new tests.

        * history/PageCache.cpp:
        (WebCore::pageCacheLogPrefix):
        (WebCore::pageCacheLog):
        (WebCore::logCanCacheFrameDecision):
        (WebCore::logCanCachePageDecision):
        (WebCore::PageCache::canCachePageContainingThisFrame):
        (WebCore::PageCache::canCache):
        (WebCore::PageCache::add):
        (WebCore::PageCache::get):
        * history/PageCache.h:
        * loader/DocumentLoader.cpp:
        (WebCore::DocumentLoader::commitIfReady):
        * loader/FrameLoader.cpp:
        (WebCore::FrameLoader::commitProvisionalLoad):
        (WebCore::FrameLoader::prepareForCachedPageRestore):
        (WebCore::FrameLoader::continueLoadAfterNavigationPolicy):
        (WebCore::FrameLoader::loadProvisionalItemFromCachedPage):
        (WebCore::FrameLoader::navigateToDifferentDocument):
        * loader/FrameLoader.h:
        (WebCore::FrameLoader::quickRedirectComing):
        * svg/graphics/SVGImage.cpp:
        (WebCore::SVGImage::dataChanged):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@60688 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 4bfbcc11
2010-06-04 Nate Chapin <japhet@chromium.org>
Reviewed by Adam Barth.
Factor PageCache functionality out of FrameLoader and into
PageCache itself.
https://bugs.webkit.org/show_bug.cgi?id=39382
Refactor only, so no new tests.
* history/PageCache.cpp:
(WebCore::pageCacheLogPrefix):
(WebCore::pageCacheLog):
(WebCore::logCanCacheFrameDecision):
(WebCore::logCanCachePageDecision):
(WebCore::PageCache::canCachePageContainingThisFrame):
(WebCore::PageCache::canCache):
(WebCore::PageCache::add):
(WebCore::PageCache::get):
* history/PageCache.h:
* loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::commitIfReady):
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::commitProvisionalLoad):
(WebCore::FrameLoader::prepareForCachedPageRestore):
(WebCore::FrameLoader::continueLoadAfterNavigationPolicy):
(WebCore::FrameLoader::loadProvisionalItemFromCachedPage):
(WebCore::FrameLoader::navigateToDifferentDocument):
* loader/FrameLoader.h:
(WebCore::FrameLoader::quickRedirectComing):
* svg/graphics/SVGImage.cpp:
(WebCore::SVGImage::dataChanged):
2010-06-04 Ilya Tikhonovsky <loislo@chromium.org>
Reviewed by Pavel Feldman.
......
......@@ -26,11 +26,21 @@
#include "config.h"
#include "PageCache.h"
#include "ApplicationCacheHost.h"
#include "BackForwardList.h"
#include "Cache.h"
#include "CachedPage.h"
#include "DOMWindow.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "HistoryItem.h"
#include "Logging.h"
#include "Page.h"
#include "Settings.h"
#include "SharedWorkerRepository.h"
#include "SystemTime.h"
#include <wtf/CurrentTime.h>
......@@ -40,6 +50,173 @@ 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), String::format(__VA_ARGS__))
static bool logCanCacheFrameDecision(Frame* frame, int indentLevel)
{
// Only bother logging for frames that have actually loaded and have content.
if (frame->loader()->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 (%s) to (%s):", currentURL.string().utf8().data(), newURL.string().utf8().data());
else
PCLOG(" Determining if subframe with URL (%s) can be cached:", currentURL.string().utf8().data());
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()->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()->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->backForwardList()->enabled()) {
PCLOG(" -The back/forward list is disabled");
cannotCache = true;
}
if (!(page->backForwardList()->capacity() > 0)) {
PCLOG(" -The back/forward list has a 0 capacity");
cannotCache = true;
}
if (!page->settings()->usesPageCache()) {
PCLOG(" -Page settings says b/f cache disabled");
cannotCache = true;
}
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;
......@@ -54,6 +231,74 @@ PageCache::PageCache()
, 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()->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->backForwardList()->enabled()
&& page->backForwardList()->capacity() > 0
&& page->settings()->usesPageCache()
&& loadType != FrameLoadTypeReload
&& loadType != FrameLoadTypeReloadFromOrigin
&& loadType != FrameLoadTypeSame;
}
void PageCache::setCapacity(int capacity)
{
......@@ -80,10 +325,11 @@ int PageCache::autoreleasedPageCount() const
return m_autoreleaseSet.size();
}
void PageCache::add(PassRefPtr<HistoryItem> prpItem, PassRefPtr<CachedPage> cachedPage)
void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page* page)
{
ASSERT(prpItem);
ASSERT(cachedPage);
ASSERT(page);
ASSERT(canCache(page));
HistoryItem* item = prpItem.releaseRef(); // Balanced in remove().
......@@ -91,13 +337,31 @@ void PageCache::add(PassRefPtr<HistoryItem> prpItem, PassRefPtr<CachedPage> cach
if (item->m_cachedPage)
remove(item);
item->m_cachedPage = cachedPage;
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.
......
......@@ -35,18 +35,22 @@
namespace WebCore {
class CachedPage;
class Frame;
class HistoryItem;
class Page;
class PageCache : public Noncopyable {
public:
friend PageCache* pageCache();
static bool canCache(Page*);
void setCapacity(int); // number of pages to cache
int capacity() { return m_capacity; }
void add(PassRefPtr<HistoryItem>, PassRefPtr<CachedPage>); // Prunes if capacity() is exceeded.
void add(PassRefPtr<HistoryItem>, Page*); // Prunes if capacity() is exceeded.
void remove(HistoryItem*);
CachedPage* get(HistoryItem* item) { return item ? item->m_cachedPage.get() : 0; }
CachedPage* get(HistoryItem* item);
void releaseAutoreleasedPagesNow();
......@@ -59,6 +63,8 @@ namespace WebCore {
PageCache(); // Use pageCache() instead.
~PageCache(); // Not implemented to make sure nobody accidentally calls delete -- WebCore does not delete singletons.
static bool canCachePageContainingThisFrame(Frame*);
void addToLRUList(HistoryItem*); // Adds to the head of the list.
void removeFromLRUList(HistoryItem*);
......
......@@ -255,7 +255,7 @@ void DocumentLoader::commitIfReady()
{
if (m_gotFirstByte && !m_committed) {
m_committed = true;
frameLoader()->commitProvisionalLoad(0);
frameLoader()->commitProvisionalLoad();
}
}
......
......@@ -1413,208 +1413,6 @@ static inline bool frameContainsWMLContent(Frame* frame)
}
#endif
bool FrameLoader::canCachePageContainingThisFrame()
{
for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
if (!child->loader()->canCachePageContainingThisFrame())
return false;
}
return m_documentLoader
&& m_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.
&& !m_containsPlugIns
&& !m_URL.protocolIs("https")
&& (!m_frame->domWindow() || !m_frame->domWindow()->hasEventListeners(eventNames().unloadEvent))
#if ENABLE(DATABASE)
&& !m_frame->document()->hasOpenDatabases()
#endif
#if ENABLE(SHARED_WORKERS)
&& !SharedWorkerRepository::hasSharedWorkers(m_frame->document())
#endif
&& !m_frame->document()->usingGeolocation()
&& history()->currentItem()
&& !m_quickRedirectComing
&& !m_documentLoader->isLoadingInAPISense()
&& !m_documentLoader->isStopping()
&& m_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.
&& m_documentLoader->applicationCacheHost()->canCacheInPageCache()
#endif
#if ENABLE(WML)
&& !frameContainsWMLContent(m_frame)
#endif
&& m_client->canCachePage()
;
}
bool FrameLoader::canCachePage()
{
#ifndef NDEBUG
logCanCachePageDecision();
#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 = this->loadType();
return !m_frame->tree()->parent()
&& canCachePageContainingThisFrame()
&& m_frame->page()
&& m_frame->page()->backForwardList()->enabled()
&& m_frame->page()->backForwardList()->capacity() > 0
&& m_frame->page()->settings()->usesPageCache()
&& loadType != FrameLoadTypeReload
&& loadType != FrameLoadTypeReloadFromOrigin
&& loadType != FrameLoadTypeSame
;
}
#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), String::format(__VA_ARGS__))
void FrameLoader::logCanCachePageDecision()
{
// Only bother logging for main frames that have actually loaded and have content.
if (m_creatingInitialEmptyDocument)
return;
KURL currentURL = m_documentLoader ? m_documentLoader->url() : KURL();
if (currentURL.isEmpty())
return;
int indentLevel = 0;
PCLOG("--------\n Determining if page can be cached:");
bool cannotCache = !logCanCacheFrameDecision(1);
FrameLoadType loadType = this->loadType();
do {
if (m_frame->tree()->parent())
{ PCLOG(" -Frame has a parent frame"); cannotCache = true; }
if (!m_frame->page()) {
PCLOG(" -There is no Page object");
cannotCache = true;
break;
}
if (!m_frame->page()->backForwardList()->enabled())
{ PCLOG(" -The back/forward list is disabled"); cannotCache = true; }
if (!(m_frame->page()->backForwardList()->capacity() > 0))
{ PCLOG(" -The back/forward list has a 0 capacity"); cannotCache = true; }
if (!m_frame->page()->settings()->usesPageCache())
{ PCLOG(" -Page settings says b/f cache disabled"); cannotCache = true; }
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; }
} while (false);
PCLOG(cannotCache ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------");
}
bool FrameLoader::logCanCacheFrameDecision(int indentLevel)
{
// Only bother logging for frames that have actually loaded and have content.
if (m_creatingInitialEmptyDocument)
return false;
KURL currentURL = m_documentLoader ? m_documentLoader->url() : KURL();
if (currentURL.isEmpty())
return false;
PCLOG("+---");
KURL newURL = m_provisionalDocumentLoader ? m_provisionalDocumentLoader->url() : KURL();
if (!newURL.isEmpty())
PCLOG(" Determining if frame can be cached navigating from (%s) to (%s):", currentURL.string().utf8().data(), newURL.string().utf8().data());
else
PCLOG(" Determining if subframe with URL (%s) can be cached:", currentURL.string().utf8().data());
bool cannotCache = false;
do {
if (!m_documentLoader) {
PCLOG(" -There is no DocumentLoader object");
cannotCache = true;
break;
}
if (!m_documentLoader->mainDocumentError().isNull())
{ PCLOG(" -Main document has an error"); cannotCache = true; }
if (m_containsPlugIns)
{ PCLOG(" -Frame contains plugins"); cannotCache = true; }
if (m_URL.protocolIs("https"))
{ PCLOG(" -Frame is HTTPS"); cannotCache = true; }
if (m_frame->domWindow() && m_frame->domWindow()->hasEventListeners(eventNames().unloadEvent))
{ PCLOG(" -Frame has an unload event listener"); cannotCache = true; }
#if ENABLE(DATABASE)
if (m_frame->document()->hasOpenDatabases())
{ PCLOG(" -Frame has open database handles"); cannotCache = true; }
#endif
#if ENABLE(SHARED_WORKERS)
if (SharedWorkerRepository::hasSharedWorkers(m_frame->document()))
{ PCLOG(" -Frame has associated SharedWorkers"); cannotCache = true; }
#endif
if (m_frame->document()->usingGeolocation())
{ PCLOG(" -Frame uses Geolocation"); cannotCache = true; }
if (!history()->currentItem())
{ PCLOG(" -No current history item"); cannotCache = true; }
if (m_quickRedirectComing)
{ PCLOG(" -Quick redirect is coming"); cannotCache = true; }
if (m_documentLoader->isLoadingInAPISense())
{ PCLOG(" -DocumentLoader is still loading in API sense"); cannotCache = true; }
if (m_documentLoader->isStopping())
{ PCLOG(" -DocumentLoader is in the middle of stopping"); cannotCache = true; }
if (!m_frame->document()->canSuspendActiveDOMObjects())
{ PCLOG(" -The document cannot suspect its active DOM Objects"); cannotCache = true; }
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
if (!m_documentLoader->applicationCacheHost()->canCacheInPageCache())
{ PCLOG(" -The DocumentLoader uses an application cache"); cannotCache = true; }
#endif
if (!m_client->canCachePage())
{ PCLOG(" -The client says this frame cannot be cached"); cannotCache = true; }
} while (false);
for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
if (!child->loader()->logCanCacheFrameDecision(indentLevel + 1))
cannotCache = true;
PCLOG(cannotCache ? " Frame CANNOT be cached" : " Frame CAN be cached");
PCLOG("+---");
return !cannotCache;