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 "ScrollingCoordinator.h"
#include "SerializedScriptValue.h"
#include "SharedStringHash.h"
#include "VisitedLinkStore.h"
#include <wtf/text/CString.h>
namespace WebCore {
static inline void addVisitedLink(Page& page, const URL& url)
{
page.visitedLinkStore().addVisitedLink(page, computeSharedStringHash(url.string()));
}
HistoryController::HistoryController(Frame& frame)
: m_frame(frame)
, m_frameLoadComplete(true)
, m_defersLoading(false)
{
}
HistoryController::~HistoryController() = default;
void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
{
FrameView* frameView = m_frame.view();
if (!item || !frameView)
return;
if (m_frame.document()->pageCacheState() != Document::NotInPageCache)
item->setScrollPosition(frameView->cachedScrollPosition());
else
item->setScrollPosition(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() / page->viewScaleFactor());
#if PLATFORM(IOS)
item->setObscuredInsets(page->obscuredInsets());
#endif
}
m_frame.loader().client().saveViewStateToItem(*item);
item->notifyChanged();
}
void HistoryController::clearScrollPositionAndViewState()
{
if (!m_currentItem)
return;
m_currentItem->clearScrollPosition();
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();
auto desiredScrollPosition = m_currentItem->shouldRestoreScrollPosition() ? m_currentItem->scrollPosition() : view->scrollPosition();
LOG(Scrolling, "HistoryController::restoreScrollPositionAndViewState scrolling to %d,%d", desiredScrollPosition.x(), desiredScrollPosition.y());
if (page && m_frame.isMainFrame() && m_currentItem->pageScaleFactor())
page->setPageScaleFactor(m_currentItem->pageScaleFactor() * page->viewScaleFactor(), desiredScrollPosition);
else
view->setScrollPosition(desiredScrollPosition);
if (m_frame.isMainFrame()) {
auto adjustedDesiredScrollPosition = view->adjustScrollPositionWithinRange(desiredScrollPosition);
if (desiredScrollPosition == adjustedDesiredScrollPosition)
m_frame.loader().client().didRestoreScrollPosition();
}
}
#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;
ASSERT(m_frame.document());
Document& document = *m_frame.document();
if (item->isCurrentDocument(document) && document.hasLivingRenderTree()) {
if (DocumentLoader* documentLoader = document.loader())
item->setShouldOpenExternalURLsPolicy(documentLoader->shouldOpenExternalURLsPolicyToPropagate());
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::ReloadExpiredOnly:
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;
m_frame.loader().documentLoader()->setShouldOpenExternalURLsPolicy(m_currentItem->shouldOpenExternalURLsPolicy());
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 (!currentItem())
return;
std::unique_ptr<CachedPage> cachedPage = PageCache::singleton().take(*currentItem(), m_frame.page());
if (!cachedPage)
return;
ASSERT(cachedPage->document() == m_frame.document());
if (cachedPage->document() == m_frame.document()) {
cachedPage->document()->setPageCacheState(Document::NotInPageCache);
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)
{
LOG(History, "HistoryController %p goToItem %p type=%d", this, &targetItem, static_cast<int>(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, m_deferredFrameLoadType);
m_deferredItem = nullptr;
}
}
void HistoryController::updateForBackForwardNavigation()
{
LOG(History, "HistoryController %p updateForBackForwardNavigation: Updating History for back/forward navigation in frame %p (main frame %d) %s", this, &m_frame, m_frame.isMainFrame(), m_frame.loader().documentLoader() ? m_frame.loader().documentLoader()->url().string().utf8().data() : "");
if (!m_frameLoadComplete)
saveScrollPositionAndViewStateToItem(m_previousItem.get());
updateCurrentItem();
}
void HistoryController::updateForReload()
{
LOG(History, "HistoryController %p updateForReload: Updating History for reload in frame %p (main frame %d) %s", this, &m_frame, m_frame.isMainFrame(), m_frame.loader().documentLoader() ? m_frame.loader().documentLoader()->url().string().utf8().data() : "");
if (m_currentItem) {
PageCache::singleton().remove(*m_currentItem);
if (m_frame.loader().loadType() == FrameLoadType::Reload || m_frame.loader().loadType() == FrameLoadType::ReloadFromOrigin)
saveScrollPositionAndViewStateToItem(m_currentItem.get());
m_currentItem->clearChildren();
}
updateCurrentItem();
}
void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
{
LOG(History, "HistoryController %p updateForStandardLoad: Updating History for standard load in frame %p (main frame %d) %s", this, &m_frame, m_frame.isMainFrame(), m_frame.loader().documentLoader()->url().string().ascii().data());
FrameLoader& frameLoader = m_frame.loader();
bool needPrivacy = m_frame.page() ? m_frame.page()->usesEphemeralSession() : true;
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()
{
LOG(History, "HistoryController %p updateForRedirectWithLockedBackForwardList: Updating History for redirect load in frame %p (main frame %d) %s", this, &m_frame, m_frame.isMainFrame(), m_frame.loader().documentLoader() ? m_frame.loader().documentLoader()->url().string().utf8().data() : "");
bool needPrivacy = m_frame.page() ? m_frame.page()->usesEphemeralSession() : true;
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()
{
LOG(History, "HistoryController %p updateForClientRedirect: Updating History for client redirect in frame %p (main frame %d) %s", this, &m_frame, m_frame.isMainFrame(), m_frame.loader().documentLoader() ? m_frame.loader().documentLoader()->url().string().utf8().data() : "");
if (m_currentItem) {
m_currentItem->clearDocumentState();
m_currentItem->clearScrollPosition();
}
bool needPrivacy = m_frame.page() ? m_frame.page()->usesEphemeralSession() : true;
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();
LOG(History, "HistoryController %p updateForCommit: Updating History for commit in frame %p (main frame %d) %s", this, &m_frame, m_frame.isMainFrame(), m_frame.loader().documentLoader() ? m_frame.loader().documentLoader()->url().string().utf8().data() : "");
FrameLoadType type = frameLoader.loadType();
if (isBackForwardLoadType(type)
|| isReplaceLoadTypeWithProvisionalItem(type)
|| (isReloadTypeWithProvisionalItem(type) && !frameLoader.provisionalDocumentLoader()->unreachableURL().isEmpty())) {
ASSERT(m_provisionalItem);
setCurrentItem(m_provisionalItem.get());
m_provisionalItem = nullptr;
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, 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 = nullptr;
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;
Page* page = m_frame.page();
if (!page)
return;
if (page->usesEphemeralSession())
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))
return;
setCurrentItem(m_provisionalItem.get());
m_provisionalItem = nullptr;
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 && equalIgnoringASCIICase(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();
StringWithDirection title = documentLoader->title();
item.setURL(url);
item.setTarget(m_frame.tree().uniqueName());
item.setTitle(title.string);
item.setOriginalURLString(originalURL.string());
if (!unreachableURL.isEmpty() || documentLoader->response().httpStatusCode() >= 400)
item.setLastVisitWasFailure(true);
item.setShouldOpenExternalURLsPolicy(documentLoader->shouldOpenExternalURLsPolicyToPropagate());
item.setFormInfoFromRequest(documentLoader->request());
}
Ref<HistoryItem> HistoryController::createItem()
{
Ref<HistoryItem> item = HistoryItem::create();
initializeItem(item);
setCurrentItem(item.ptr());
return item;
}
Ref<HistoryItem> HistoryController::createItemTree(Frame& targetFrame, bool clipAtTarget)
{
Ref<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)
{
if (!itemsAreClones(item, fromItem))
return;
m_provisionalItem = &item;
for (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, fromChildItem);
}
}
void HistoryController::recursiveGoToItem(HistoryItem& item, HistoryItem* fromItem, FrameLoadType type)
{
if (!itemsAreClones(item, fromItem)) {
m_frame.loader().loadItem(item, type);
return;
}
for (auto& childItem : item.children()) {
const String& childFrameName = childItem->target();
HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
ASSERT(fromChildItem);
if (Frame* childFrame = m_frame.tree().child(childFrameName))
childFrame->loader().history().recursiveGoToItem(childItem, fromChildItem, type);
}
}
bool HistoryController::itemsAreClones(HistoryItem& item1, HistoryItem* item2) const
{
return 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 auto& childItems = item->children();
if (childItems.size() != m_frame.tree().childCount())
return false;
for (auto& item : childItems) {
if (!m_frame.tree().child(item->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();
Ref<HistoryItem> topItem = frameLoader.history().createItemTree(m_frame, doClip);
LOG(History, "HistoryController %p updateBackForwardListClippedAtTarget: Adding backforward item %p in frame %p (main frame %d) %s", this, topItem.ptr(), &m_frame, m_frame.isMainFrame(), m_frame.loader().documentLoader()->url().string().utf8().data());
page->backForward().addItem(WTFMove(topItem));
}
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);
m_currentItem->setIsTargetItem(isTargetItem);
} else {
m_currentItem->setFormInfoFromRequest(documentLoader->request());
}
}
void HistoryController::pushState(RefPtr<SerializedScriptValue>&& stateObject, const String& title, const String& urlString)
{
if (!m_currentItem)
return;
Page* page = m_frame.page();
ASSERT(page);
bool shouldRestoreScrollPosition = m_currentItem->shouldRestoreScrollPosition();
Ref<HistoryItem> topItem = m_frame.mainFrame().loader().history().createItemTree(m_frame, false);
m_currentItem->setTitle(title);
m_currentItem->setStateObject(WTFMove(stateObject));
m_currentItem->setURLString(urlString);
m_currentItem->setShouldRestoreScrollPosition(shouldRestoreScrollPosition);
LOG(History, "HistoryController %p pushState: Adding top item %p, setting url of current item %p to %s, scrollRestoration is %s", this, topItem.ptr(), m_currentItem.get(), urlString.ascii().data(), topItem->shouldRestoreScrollPosition() ? "auto" : "manual");
page->backForward().addItem(WTFMove(topItem));
if (m_frame.page()->usesEphemeralSession())
return;
addVisitedLink(*page, URL(ParsedURLString, urlString));
m_frame.loader().client().updateGlobalHistory();
}
void HistoryController::replaceState(RefPtr<SerializedScriptValue>&& stateObject, const String& title, const String& urlString)
{
if (!m_currentItem)
return;
LOG(History, "HistoryController %p replaceState: Setting url of current item %p to %s scrollRestoration %s", this, m_currentItem.get(), urlString.ascii().data(), m_currentItem->shouldRestoreScrollPosition() ? "auto" : "manual");
if (!urlString.isEmpty())
m_currentItem->setURLString(urlString);
m_currentItem->setTitle(title);
m_currentItem->setStateObject(WTFMove(stateObject));
m_currentItem->setFormData(nullptr);
m_currentItem->setFormContentType(String());
ASSERT(m_frame.page());
if (m_frame.page()->usesEphemeralSession())
return;
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_currentItem = item;
}
}