HistoryController.cpp [plain text]
#include "config.h"
#include "HistoryController.h"
#include "BackForwardController.h"
#include "CachedPage.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameLoaderStateMachine.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "HistoryItem.h"
#include "Logging.h"
#include "MainFrame.h"
#include "Page.h"
#include "PageCache.h"
#include "PageGroup.h"
#include "ScrollingCoordinator.h"
#include "VisitedLinkStore.h"
#include <wtf/text/CString.h>
namespace WebCore {
static inline void addVisitedLink(Page& page, const URL& url)
{
page.visitedLinkStore().addVisitedLink(page, visitedLinkHash(url.string()));
}
HistoryController::HistoryController(Frame& frame)
: m_frame(frame)
, m_frameLoadComplete(true)
, m_defersLoading(false)
{
}
HistoryController::~HistoryController()
{
}
void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
{
FrameView* frameView = m_frame.view();
if (!item || !frameView)
return;
if (m_frame.document()->inPageCache())
item->setScrollPoint(frameView->cachedScrollPosition());
else
item->setScrollPoint(frameView->scrollPosition());
#if PLATFORM(IOS)
item->setExposedContentRect(frameView->exposedContentRect());
item->setUnobscuredContentRect(frameView->unobscuredContentRect());
#endif
Page* page = m_frame.page();
if (page && m_frame.isMainFrame())
item->setPageScaleFactor(page->pageScaleFactor());
m_frame.loader().client().saveViewStateToItem(item);
}
void HistoryController::clearScrollPositionAndViewState()
{
if (!m_currentItem)
return;
m_currentItem->clearScrollPoint();
m_currentItem->setPageScaleFactor(0);
}
void HistoryController::restoreScrollPositionAndViewState()
{
if (!m_frame.loader().stateMachine().committedFirstRealDocumentLoad())
return;
ASSERT(m_currentItem);
if (!m_currentItem)
return;
FrameView* view = m_frame.view();
if (view) {
Page* page = m_frame.page();
if (page && m_frame.isMainFrame()) {
if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator())
scrollingCoordinator->frameViewRootLayerDidChange(view);
}
}
m_frame.loader().client().restoreViewState();
#if !PLATFORM(IOS)
if (view && !view->wasScrolledByUser()) {
Page* page = m_frame.page();
if (page && m_frame.isMainFrame() && m_currentItem->pageScaleFactor())
page->setPageScaleFactor(m_currentItem->pageScaleFactor(), m_currentItem->scrollPoint());
else
view->setScrollPosition(m_currentItem->scrollPoint());
}
#endif
}
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) && document->hasLivingRenderTree()) {
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()
{
switch (m_frame.loader().loadType()) {
case FrameLoadType::Reload:
case FrameLoadType::ReloadFromOrigin:
case FrameLoadType::Same:
case FrameLoadType::Replace:
return;
case FrameLoadType::Back:
case FrameLoadType::Forward:
case FrameLoadType::IndexedBackForward:
case FrameLoadType::RedirectWithLockedBackForwardList:
case FrameLoadType::Standard:
break;
}
if (!m_currentItem)
return;
if (m_frame.loader().requestedHistoryItem() != m_currentItem.get())
return;
if (m_frame.loader().documentLoader()->isClientRedirect())
return;
LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame.tree().uniqueName().string().utf8().data(), m_currentItem.get());
m_frame.document()->setStateForNewFormElements(m_currentItem->documentState());
}
void HistoryController::invalidateCurrentItemCachedPage()
{
if (!pageCache()->get(currentItem()))
return;
std::unique_ptr<CachedPage> cachedPage = pageCache()->take(currentItem());
ASSERT(cachedPage->document() == m_frame.document());
if (cachedPage->document() == m_frame.document()) {
cachedPage->document()->setInPageCache(false);
cachedPage->clear();
}
}
bool HistoryController::shouldStopLoadingForHistoryItem(HistoryItem* targetItem) const
{
if (!m_currentItem)
return false;
if (m_currentItem->shouldDoSameDocumentNavigationTo(targetItem))
return false;
return true;
}
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());
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() == FrameLoadType::Reload || m_frame.loader().loadType() == FrameLoadType::ReloadFromOrigin)
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();
bool needPrivacy = m_frame.page()->usesEphemeralSession();
const URL& 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
bool needPrivacy = m_frame.page()->usesEphemeralSession();
const URL& 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().currentItem())
parentFrame->loader().history().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();
}
bool needPrivacy = m_frame.page()->usesEphemeralSession();
const URL& 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)
|| (isReloadTypeWithProvisionalItem(type) && !frameLoader.provisionalDocumentLoader()->unreachableURL().isEmpty())) {
ASSERT(m_provisionalItem);
setCurrentItem(m_provisionalItem.get());
m_provisionalItem = 0;
m_frame.mainFrame().loader().history().recursiveUpdateForCommit();
}
}
bool HistoryController::isReplaceLoadTypeWithProvisionalItem(FrameLoadType type)
{
return type == FrameLoadType::Replace && m_provisionalItem;
}
bool HistoryController::isReloadTypeWithProvisionalItem(FrameLoadType type)
{
return (type == FrameLoadType::Reload || type == FrameLoadType::ReloadFromOrigin) && m_provisionalItem;
}
void HistoryController::recursiveUpdateForCommit()
{
if (!m_provisionalItem)
return;
if (m_currentItem && itemsAreClones(m_currentItem.get(), m_provisionalItem.get())) {
ASSERT(m_frameLoadComplete);
saveDocumentState();
saveScrollPositionAndViewStateToItem(m_currentItem.get());
if (FrameView* view = m_frame.view())
view->setWasScrolledByUser(false);
setCurrentItem(m_provisionalItem.get());
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;
if (m_frame.page()->usesEphemeralSession())
return;
Page* page = m_frame.page();
if (!page)
return;
addVisitedLink(*page, m_frame.document()->url());
m_frame.mainFrame().loader().history().recursiveUpdateForSameDocumentNavigation();
if (m_currentItem) {
m_currentItem->setURL(m_frame.document()->url());
m_frame.loader().client().updateGlobalHistory();
}
}
void HistoryController::recursiveUpdateForSameDocumentNavigation()
{
if (!m_provisionalItem)
return;
if (m_currentItem && !m_currentItem->shouldDoSameDocumentNavigationTo(m_provisionalItem.get()))
return;
setCurrentItem(m_provisionalItem.get());
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_frame.loader().client().willChangeCurrentHistoryItem();
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::clearPreviousItem()
{
m_previousItem = nullptr;
for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
child->loader().history().clearPreviousItem();
}
void HistoryController::setProvisionalItem(HistoryItem* item)
{
m_provisionalItem = item;
}
void HistoryController::initializeItem(HistoryItem* item)
{
DocumentLoader* documentLoader = m_frame.loader().documentLoader();
ASSERT(documentLoader);
URL unreachableURL = documentLoader->unreachableURL();
URL url;
URL 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());
setCurrentItem(item.get());
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)
{
ASSERT(item);
if (!itemsAreClones(item, fromItem))
return;
m_provisionalItem = item;
for (const auto& childItem : item->children()) {
const String& childFrameName = childItem->target();
HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
ASSERT(fromChildItem);
Frame* childFrame = m_frame.tree().child(childFrameName);
ASSERT(childFrame);
childFrame->loader().history().recursiveSetProvisionalItem(childItem.get(), fromChildItem);
}
}
void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
{
ASSERT(item);
if (!itemsAreClones(item, fromItem)) {
m_frame.loader().loadItem(item, type);
return;
}
for (const auto& childItem : item->children()) {
const String& childFrameName = childItem->target();
HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
ASSERT(fromChildItem);
Frame* childFrame = m_frame.tree().child(childFrameName);
ASSERT(childFrame);
childFrame->loader().history().recursiveGoToItem(childItem.get(), fromChildItem, type);
}
}
bool HistoryController::itemsAreClones(HistoryItem* item1, HistoryItem* item2) const
{
return item1
&& item2
&& 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;
FrameLoader& frameLoader = m_frame.mainFrame().loader();
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 = m_frame.mainFrame().loader().history().createItemTree(m_frame, false);
m_currentItem->setTitle(title);
m_currentItem->setStateObject(stateObject);
m_currentItem->setURLString(urlString);
page->backForward().addItem(topItem.release());
if (m_frame.page()->usesEphemeralSession())
return;
addVisitedLink(*page, URL(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);
m_currentItem->setFormData(0);
m_currentItem->setFormContentType(String());
if (m_frame.page()->usesEphemeralSession())
return;
ASSERT(m_frame.page());
addVisitedLink(*m_frame.page(), URL(ParsedURLString, urlString));
m_frame.loader().client().updateGlobalHistory();
}
void HistoryController::replaceCurrentItem(HistoryItem* item)
{
if (!item)
return;
m_previousItem = nullptr;
if (m_provisionalItem)
m_provisionalItem = item;
else {
m_frame.loader().client().willChangeCurrentHistoryItem();
m_currentItem = item;
}
}
}