HistoryController.cpp [plain text]
#include "config.h"
#include "HistoryController.h"
#include "BackForwardList.h"
#include "CachedPage.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "HistoryItem.h"
#include "Logging.h"
#include "Page.h"
#include "PageCache.h"
#include "PageGroup.h"
#include "Settings.h"
#include <wtf/text/CString.h>
namespace WebCore {
HistoryController::HistoryController(Frame* frame)
: m_frame(frame)
{
}
HistoryController::~HistoryController()
{
}
void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
{
if (!item || !m_frame->view())
return;
item->setScrollPoint(m_frame->view()->scrollPosition());
m_frame->loader()->client()->saveViewStateToItem(item);
}
void HistoryController::restoreScrollPositionAndViewState()
{
if (!m_frame->loader()->committedFirstRealDocumentLoad())
return;
ASSERT(m_currentItem);
if (!m_currentItem)
return;
m_frame->loader()->client()->restoreViewState();
}
void HistoryController::updateBackForwardListForFragmentScroll()
{
updateBackForwardListClippedAtTarget(false);
if (!m_previousItem)
return;
ASSERT(m_currentItem);
m_currentItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
}
void HistoryController::saveDocumentState()
{
if (m_frame->loader()->creatingInitialEmptyDocument())
return;
HistoryItem* item = m_previousItem ? m_previousItem.get() : m_currentItem.get();
if (!item)
return;
Document* document = m_frame->document();
ASSERT(document);
if (item->isCurrentDocument(document)) {
LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame->tree()->name().string().utf8().data(), item);
item->setDocumentState(document->formElementsState());
}
}
void HistoryController::saveDocumentAndScrollState()
{
for (Frame* frame = m_frame; frame; frame = frame->tree()->traverseNext(m_frame)) {
frame->loader()->history()->saveDocumentState();
frame->loader()->history()->saveScrollPositionAndViewStateToItem(frame->loader()->history()->currentItem());
}
}
void HistoryController::restoreDocumentState()
{
Document* doc = m_frame->document();
HistoryItem* itemToRestore = 0;
switch (m_frame->loader()->loadType()) {
case FrameLoadTypeReload:
case FrameLoadTypeReloadFromOrigin:
case FrameLoadTypeSame:
case FrameLoadTypeReplace:
break;
case FrameLoadTypeBack:
case FrameLoadTypeBackWMLDeckNotAccessible:
case FrameLoadTypeForward:
case FrameLoadTypeIndexedBackForward:
case FrameLoadTypeRedirectWithLockedBackForwardList:
case FrameLoadTypeStandard:
itemToRestore = m_currentItem.get();
}
if (!itemToRestore)
return;
LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame->tree()->name().string().utf8().data(), itemToRestore);
doc->setStateForNewFormElements(itemToRestore->documentState());
}
void HistoryController::invalidateCurrentItemCachedPage()
{
CachedPage* cachedPage = pageCache()->get(currentItem());
ASSERT(!cachedPage || cachedPage->document() == m_frame->document());
if (cachedPage && cachedPage->document() == m_frame->document()) {
cachedPage->document()->setInPageCache(false);
cachedPage->clear();
}
if (cachedPage)
pageCache()->remove(currentItem());
}
void HistoryController::goToItem(HistoryItem* targetItem, FrameLoadType type)
{
ASSERT(!m_frame->tree()->parent());
Page* page = m_frame->page();
if (!page)
return;
if (!m_frame->loader()->client()->shouldGoToHistoryItem(targetItem))
return;
BackForwardList* bfList = page->backForwardList();
HistoryItem* currentItem = bfList->currentItem();
bfList->goToItem(targetItem);
Settings* settings = m_frame->settings();
page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : targetItem);
recursiveGoToItem(targetItem, currentItem, type);
}
bool HistoryController::urlsMatchItem(HistoryItem* item) const
{
const KURL& currentURL = m_frame->loader()->documentLoader()->url();
if (!equalIgnoringFragmentIdentifier(currentURL, item->url()))
return false;
const HistoryItemVector& childItems = item->children();
unsigned size = childItems.size();
for (unsigned i = 0; i < size; ++i) {
Frame* childFrame = m_frame->tree()->child(childItems[i]->target());
if (childFrame && !childFrame->loader()->history()->urlsMatchItem(childItems[i].get()))
return false;
}
return true;
}
void HistoryController::updateForBackForwardNavigation()
{
#if !LOG_DISABLED
if (m_frame->loader()->documentLoader())
LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
#endif
saveScrollPositionAndViewStateToItem(m_previousItem.get());
}
void HistoryController::updateForReload()
{
#if !LOG_DISABLED
if (m_frame->loader()->documentLoader())
LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
#endif
if (m_currentItem) {
pageCache()->remove(m_currentItem.get());
if (m_frame->loader()->loadType() == FrameLoadTypeReload || m_frame->loader()->loadType() == FrameLoadTypeReloadFromOrigin)
saveScrollPositionAndViewStateToItem(m_currentItem.get());
if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty())
m_currentItem->setURL(m_frame->loader()->documentLoader()->requestURL());
}
}
void HistoryController::updateForStandardLoad()
{
LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data());
FrameLoader* frameLoader = m_frame->loader();
Settings* settings = m_frame->settings();
bool needPrivacy = !settings || settings->privateBrowsingEnabled();
const KURL& historyURL = frameLoader->documentLoader()->urlForHistory();
if (!frameLoader->documentLoader()->isClientRedirect()) {
if (!historyURL.isEmpty()) {
updateBackForwardListClippedAtTarget(true);
if (!needPrivacy) {
frameLoader->client()->updateGlobalHistory();
frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true);
if (frameLoader->documentLoader()->unreachableURL().isEmpty())
frameLoader->client()->updateGlobalHistoryRedirectLinks();
}
if (Page* page = m_frame->page())
page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForwardList()->currentItem());
}
} else if (frameLoader->documentLoader()->unreachableURL().isEmpty() && m_currentItem) {
m_currentItem->setURL(frameLoader->documentLoader()->url());
m_currentItem->setFormInfoFromRequest(frameLoader->documentLoader()->request());
}
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
page->group().addVisitedLink(historyURL);
if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !frameLoader->url().isEmpty())
frameLoader->client()->updateGlobalHistoryRedirectLinks();
}
}
void HistoryController::updateForRedirectWithLockedBackForwardList()
{
#if !LOG_DISABLED
if (m_frame->loader()->documentLoader())
LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
#endif
Settings* settings = m_frame->settings();
bool needPrivacy = !settings || settings->privateBrowsingEnabled();
const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
if (m_frame->loader()->documentLoader()->isClientRedirect()) {
if (!m_currentItem && !m_frame->tree()->parent()) {
if (!historyURL.isEmpty()) {
updateBackForwardListClippedAtTarget(true);
if (!needPrivacy) {
m_frame->loader()->client()->updateGlobalHistory();
m_frame->loader()->documentLoader()->setDidCreateGlobalHistoryEntry(true);
if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty())
m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
}
if (Page* page = m_frame->page())
page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForwardList()->currentItem());
}
}
if (m_currentItem) {
m_currentItem->setURL(m_frame->loader()->documentLoader()->url());
m_currentItem->setFormInfoFromRequest(m_frame->loader()->documentLoader()->request());
}
} else {
Frame* parentFrame = m_frame->tree()->parent();
if (parentFrame && parentFrame->loader()->history()->m_currentItem)
parentFrame->loader()->history()->m_currentItem->setChildItem(createItem(true));
}
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
page->group().addVisitedLink(historyURL);
if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->loader()->url().isEmpty())
m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
}
}
void HistoryController::updateForClientRedirect()
{
#if !LOG_DISABLED
if (m_frame->loader()->documentLoader())
LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
#endif
if (m_currentItem) {
m_currentItem->clearDocumentState();
m_currentItem->clearScrollPoint();
}
Settings* settings = m_frame->settings();
bool needPrivacy = !settings || settings->privateBrowsingEnabled();
const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
page->group().addVisitedLink(historyURL);
}
}
void HistoryController::updateForCommit()
{
FrameLoader* frameLoader = m_frame->loader();
#if !LOG_DISABLED
if (frameLoader->documentLoader())
LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().utf8().data());
#endif
FrameLoadType type = frameLoader->loadType();
if (isBackForwardLoadType(type) ||
((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) {
m_previousItem = m_currentItem;
ASSERT(m_provisionalItem);
m_currentItem = m_provisionalItem;
m_provisionalItem = 0;
}
}
void HistoryController::updateForSameDocumentNavigation()
{
if (m_frame->loader()->url().isEmpty())
return;
Settings* settings = m_frame->settings();
if (!settings || settings->privateBrowsingEnabled())
return;
Page* page = m_frame->page();
if (!page)
return;
page->group().addVisitedLink(m_frame->loader()->url());
}
void HistoryController::updateForFrameLoadCompleted()
{
m_previousItem = 0;
}
void HistoryController::setCurrentItem(HistoryItem* item)
{
m_currentItem = item;
}
void HistoryController::setCurrentItemTitle(const String& title)
{
if (m_currentItem)
m_currentItem->setTitle(title);
}
bool HistoryController::currentItemShouldBeReplaced() const
{
return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
}
void HistoryController::setProvisionalItem(HistoryItem* item)
{
m_provisionalItem = item;
}
PassRefPtr<HistoryItem> HistoryController::createItem(bool useOriginal)
{
DocumentLoader* docLoader = m_frame->loader()->documentLoader();
KURL unreachableURL = docLoader ? docLoader->unreachableURL() : KURL();
KURL url;
KURL originalURL;
if (!unreachableURL.isEmpty()) {
url = unreachableURL;
originalURL = unreachableURL;
} else {
originalURL = docLoader ? docLoader->originalURL() : KURL();
if (useOriginal)
url = originalURL;
else if (docLoader)
url = docLoader->requestURL();
}
LOG(History, "WebCoreHistory: Creating item for %s", url.string().ascii().data());
if (url.isEmpty())
url = blankURL();
if (originalURL.isEmpty())
originalURL = blankURL();
Frame* parentFrame = m_frame->tree()->parent();
String parent = parentFrame ? parentFrame->tree()->name() : "";
String title = docLoader ? docLoader->title() : "";
RefPtr<HistoryItem> item = HistoryItem::create(url, m_frame->tree()->name(), parent, title);
item->setOriginalURLString(originalURL.string());
if (!unreachableURL.isEmpty() || !docLoader || docLoader->response().httpStatusCode() >= 400)
item->setLastVisitWasFailure(true);
if (docLoader) {
if (useOriginal)
item->setFormInfoFromRequest(docLoader->originalRequest());
else
item->setFormInfoFromRequest(docLoader->request());
}
m_previousItem = m_currentItem;
m_currentItem = item;
return item.release();
}
PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
{
RefPtr<HistoryItem> bfItem = createItem(m_frame->tree()->parent() ? true : false);
if (m_previousItem)
saveScrollPositionAndViewStateToItem(m_previousItem.get());
if (!(clipAtTarget && m_frame == targetFrame)) {
saveDocumentState();
for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
FrameLoader* childLoader = child->loader();
bool hasChildLoaded = childLoader->frameHasLoaded();
if (!(!hasChildLoaded && childLoader->isHostedByObjectElement()))
bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget));
}
}
if (m_frame == targetFrame)
bfItem->setIsTargetItem(true);
return bfItem;
}
void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
{
ASSERT(item);
ASSERT(fromItem);
KURL itemURL = item->url();
KURL currentURL;
if (m_frame->loader()->documentLoader())
currentURL = m_frame->loader()->documentLoader()->url();
if (!item->isTargetItem() &&
itemURL == currentURL &&
((m_frame->tree()->name().isEmpty() && item->target().isEmpty()) || m_frame->tree()->name() == item->target()) &&
childFramesMatchItem(item))
{
ASSERT(!m_previousItem);
saveDocumentState();
saveScrollPositionAndViewStateToItem(m_currentItem.get());
if (FrameView* view = m_frame->view())
view->setWasScrolledByUser(false);
m_currentItem = item;
restoreDocumentState();
restoreScrollPositionAndViewState();
const HistoryItemVector& childItems = item->children();
int size = childItems.size();
for (int i = 0; i < size; ++i) {
String childFrameName = childItems[i]->target();
HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
ASSERT(fromChildItem || fromItem->isTargetItem());
Frame* childFrame = m_frame->tree()->child(childFrameName);
ASSERT(childFrame);
childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type);
}
} else {
m_frame->loader()->loadItem(item, type);
}
}
bool HistoryController::childFramesMatchItem(HistoryItem* item) const
{
const HistoryItemVector& childItems = item->children();
if (childItems.size() != m_frame->tree()->childCount())
return false;
unsigned size = childItems.size();
for (unsigned i = 0; i < size; ++i) {
if (!m_frame->tree()->child(childItems[i]->target()))
return false;
}
return true;
}
void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
{
Page* page = m_frame->page();
if (!page)
return;
if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
return;
Frame* mainFrame = page->mainFrame();
ASSERT(mainFrame);
FrameLoader* frameLoader = mainFrame->loader();
frameLoader->checkDidPerformFirstNavigation();
RefPtr<HistoryItem> item = frameLoader->history()->createItemTree(m_frame, doClip);
LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", item.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
page->backForwardList()->addItem(item);
}
void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
{
Page* page = m_frame->page();
ASSERT(page);
RefPtr<HistoryItem> item = createItemTree(m_frame, false);
ASSERT(item->isTargetItem());
item->setTitle(title);
item->setStateObject(stateObject);
item->setURLString(urlString);
item->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
page->backForwardList()->pushStateItem(item.release());
}
void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
{
if (!m_currentItem) {
ASSERT_NOT_REACHED();
return;
}
if (!urlString.isEmpty())
m_currentItem->setURLString(urlString);
m_currentItem->setTitle(title);
m_currentItem->setStateObject(stateObject);
}
}