HistoryController.cpp [plain text]
#include "config.h"
#include "HistoryController.h"
#include "BackForwardController.h"
#include "CachedPage.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameLoaderStateMachine.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>
#if USE(PLATFORM_STRATEGIES)
#include "PlatformStrategies.h"
#include "VisitedLinkStrategy.h"
#endif
namespace WebCore {
static inline void addVisitedLink(Page* page, const KURL& url)
{
#if USE(PLATFORM_STRATEGIES)
platformStrategies()->visitedLinkStrategy()->addVisitedLink(page, visitedLinkHash(url.string().characters(), url.string().length()));
#else
page->group().addVisitedLink(url);
#endif
}
HistoryController::HistoryController(Frame* frame)
: m_frame(frame)
, m_frameLoadComplete(true)
, m_defersLoading(false)
{
}
HistoryController::~HistoryController()
{
}
void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
{
if (!item || !m_frame->view())
return;
if (m_frame->document()->inPageCache())
item->setScrollPoint(m_frame->view()->cachedScrollPosition());
else
item->setScrollPoint(m_frame->view()->scrollPosition());
item->setPageScaleFactor(m_frame->pageScaleFactor());
m_frame->loader()->client()->saveViewStateToItem(item);
}
void HistoryController::restoreScrollPositionAndViewState()
{
if (!m_frame->loader()->stateMachine()->committedFirstRealDocumentLoad())
return;
ASSERT(m_currentItem);
if (!m_currentItem)
return;
m_frame->loader()->client()->restoreViewState();
}
void HistoryController::updateBackForwardListForFragmentScroll()
{
updateBackForwardListClippedAtTarget(false);
}
void HistoryController::saveDocumentState()
{
if (m_frame->loader()->stateMachine()->creatingInitialEmptyDocument())
return;
HistoryItem* item = m_frameLoadComplete ? m_currentItem.get() : m_previousItem.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()->uniqueName().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 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()->uniqueName().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());
}
bool HistoryController::shouldStopLoadingForHistoryItem(HistoryItem* targetItem) const
{
if (!m_currentItem)
return false;
if (m_currentItem->shouldDoSameDocumentNavigationTo(targetItem))
return false;
return m_frame->loader()->client()->shouldStopLoadingForHistoryItem(targetItem);
}
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;
if (m_defersLoading) {
m_deferredItem = targetItem;
m_deferredFrameLoadType = type;
return;
}
RefPtr<HistoryItem> currentItem = page->backForward()->currentItem();
page->backForward()->setCurrentItem(targetItem);
m_frame->loader()->client()->updateGlobalHistoryItemForPage();
recursiveSetProvisionalItem(targetItem, currentItem.get(), type);
recursiveGoToItem(targetItem, currentItem.get(), type);
}
void HistoryController::setDefersLoading(bool defer)
{
m_defersLoading = defer;
if (!defer && m_deferredItem) {
goToItem(m_deferredItem.get(), m_deferredFrameLoadType);
m_deferredItem = 0;
}
}
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().string().utf8().data());
#endif
if (!m_frameLoadComplete)
saveScrollPositionAndViewStateToItem(m_previousItem.get());
updateCurrentItem();
}
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().string().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());
}
updateCurrentItem();
}
void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
{
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()) {
if (updateType != UpdateAllExceptBackForwardList)
updateBackForwardListClippedAtTarget(true);
if (!needPrivacy) {
frameLoader->client()->updateGlobalHistory();
frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true);
if (frameLoader->documentLoader()->unreachableURL().isEmpty())
frameLoader->client()->updateGlobalHistoryRedirectLinks();
}
m_frame->loader()->client()->updateGlobalHistoryItemForPage();
}
} else {
updateCurrentItem();
}
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
addVisitedLink(page, historyURL);
if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !m_frame->document()->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().string().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();
}
m_frame->loader()->client()->updateGlobalHistoryItemForPage();
}
}
updateCurrentItem();
} else {
Frame* parentFrame = m_frame->tree()->parent();
if (parentFrame && parentFrame->loader()->history()->m_currentItem)
parentFrame->loader()->history()->m_currentItem->setChildItem(createItem());
}
if (!historyURL.isEmpty() && !needPrivacy) {
if (Page* page = m_frame->page())
addVisitedLink(page, historyURL);
if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->document()->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().string().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())
addVisitedLink(page, 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().string().utf8().data());
#endif
FrameLoadType type = frameLoader->loadType();
if (isBackForwardLoadType(type)
|| isReplaceLoadTypeWithProvisionalItem(type)
|| ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) {
m_frameLoadComplete = false;
m_previousItem = m_currentItem;
ASSERT(m_provisionalItem);
m_currentItem = m_provisionalItem;
m_provisionalItem = 0;
Page* page = m_frame->page();
ASSERT(page);
page->mainFrame()->loader()->history()->recursiveUpdateForCommit();
}
}
bool HistoryController::isReplaceLoadTypeWithProvisionalItem(FrameLoadType type)
{
return type == FrameLoadTypeReplace && m_provisionalItem;
}
void HistoryController::recursiveUpdateForCommit()
{
if (!m_provisionalItem)
return;
ASSERT(m_frameLoadComplete);
saveDocumentState();
saveScrollPositionAndViewStateToItem(m_currentItem.get());
if (FrameView* view = m_frame->view())
view->setWasScrolledByUser(false);
m_frameLoadComplete = false;
m_previousItem = m_currentItem;
m_currentItem = m_provisionalItem;
m_provisionalItem = 0;
restoreDocumentState();
restoreScrollPositionAndViewState();
for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
child->loader()->history()->recursiveUpdateForCommit();
}
void HistoryController::updateForSameDocumentNavigation()
{
if (m_frame->document()->url().isEmpty())
return;
Settings* settings = m_frame->settings();
if (!settings || settings->privateBrowsingEnabled())
return;
Page* page = m_frame->page();
if (!page)
return;
addVisitedLink(page, m_frame->document()->url());
page->mainFrame()->loader()->history()->recursiveUpdateForSameDocumentNavigation();
}
void HistoryController::recursiveUpdateForSameDocumentNavigation()
{
if (!m_provisionalItem)
return;
m_frameLoadComplete = false;
m_previousItem = m_currentItem;
m_currentItem = m_provisionalItem;
m_provisionalItem = 0;
for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
child->loader()->history()->recursiveUpdateForSameDocumentNavigation();
}
void HistoryController::updateForFrameLoadCompleted()
{
m_frameLoadComplete = true;
}
void HistoryController::setCurrentItem(HistoryItem* item)
{
m_frameLoadComplete = false;
m_previousItem = m_currentItem;
m_currentItem = item;
}
void HistoryController::setCurrentItemTitle(const StringWithDirection& title)
{
if (m_currentItem)
m_currentItem->setTitle(title.string());
}
bool HistoryController::currentItemShouldBeReplaced() const
{
return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
}
void HistoryController::setProvisionalItem(HistoryItem* item)
{
m_provisionalItem = item;
}
void HistoryController::initializeItem(HistoryItem* item)
{
DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
ASSERT(documentLoader);
KURL unreachableURL = documentLoader->unreachableURL();
KURL url;
KURL originalURL;
if (!unreachableURL.isEmpty()) {
url = unreachableURL;
originalURL = unreachableURL;
} else {
url = documentLoader->url();
originalURL = documentLoader->originalURL();
}
if (url.isEmpty())
url = blankURL();
if (originalURL.isEmpty())
originalURL = blankURL();
Frame* parentFrame = m_frame->tree()->parent();
String parent = parentFrame ? parentFrame->tree()->uniqueName() : "";
StringWithDirection title = documentLoader->title();
item->setURL(url);
item->setTarget(m_frame->tree()->uniqueName());
item->setParent(parent);
item->setTitle(title.string());
item->setOriginalURLString(originalURL.string());
if (!unreachableURL.isEmpty() || documentLoader->response().httpStatusCode() >= 400)
item->setLastVisitWasFailure(true);
item->setFormInfoFromRequest(documentLoader->request());
}
PassRefPtr<HistoryItem> HistoryController::createItem()
{
RefPtr<HistoryItem> item = HistoryItem::create();
initializeItem(item.get());
m_frameLoadComplete = false;
m_previousItem = m_currentItem;
m_currentItem = item;
return item.release();
}
PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
{
RefPtr<HistoryItem> bfItem = createItem();
if (!m_frameLoadComplete)
saveScrollPositionAndViewStateToItem(m_previousItem.get());
if (!clipAtTarget || m_frame != targetFrame) {
saveDocumentState();
if (m_previousItem) {
if (m_frame != targetFrame)
bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
}
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::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
{
ASSERT(item);
ASSERT(fromItem);
if (itemsAreClones(item, fromItem)) {
m_provisionalItem = item;
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);
Frame* childFrame = m_frame->tree()->child(childFrameName);
ASSERT(childFrame);
childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem, type);
}
}
}
void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
{
ASSERT(item);
ASSERT(fromItem);
if (itemsAreClones(item, fromItem)) {
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);
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::itemsAreClones(HistoryItem* item1, HistoryItem* item2) const
{
return item1 != item2
&& item1->itemSequenceNumber() == item2->itemSequenceNumber()
&& currentFramesMatchItem(item1)
&& item2->hasSameFrames(item1);
}
bool HistoryController::currentFramesMatchItem(HistoryItem* item) const
{
if ((!m_frame->tree()->uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame->tree()->uniqueName() != item->target())
return false;
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> topItem = frameLoader->history()->createItemTree(m_frame, doClip);
LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
page->backForward()->addItem(topItem.release());
}
void HistoryController::updateCurrentItem()
{
if (!m_currentItem)
return;
DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
if (!documentLoader->unreachableURL().isEmpty())
return;
if (m_currentItem->url() != documentLoader->url()) {
bool isTargetItem = m_currentItem->isTargetItem();
m_currentItem->reset();
initializeItem(m_currentItem.get());
m_currentItem->setIsTargetItem(isTargetItem);
} else {
m_currentItem->setFormInfoFromRequest(documentLoader->request());
}
}
void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
{
if (!m_currentItem)
return;
Page* page = m_frame->page();
ASSERT(page);
RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false);
m_currentItem->setTitle(title);
m_currentItem->setStateObject(stateObject);
m_currentItem->setURLString(urlString);
page->backForward()->addItem(topItem.release());
Settings* settings = m_frame->settings();
if (!settings || settings->privateBrowsingEnabled())
return;
addVisitedLink(page, KURL(ParsedURLString, urlString));
m_frame->loader()->client()->updateGlobalHistory();
}
void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
{
if (!m_currentItem)
return;
if (!urlString.isEmpty())
m_currentItem->setURLString(urlString);
m_currentItem->setTitle(title);
m_currentItem->setStateObject(stateObject);
Settings* settings = m_frame->settings();
if (!settings || settings->privateBrowsingEnabled())
return;
ASSERT(m_frame->page());
addVisitedLink(m_frame->page(), KURL(ParsedURLString, urlString));
m_frame->loader()->client()->updateGlobalHistory();
}
}