blob: c9b31b4c77508fc7ce7325f1ad10963d5f0edd15 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "core/loader/HistoryController.h"
#include "core/dom/Document.h"
#include "core/history/HistoryItem.h"
#include "core/inspector/InspectorController.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/FrameLoader.h"
#include "core/frame/Frame.h"
#include "core/frame/FrameView.h"
#include "core/page/FrameTree.h"
#include "core/page/Page.h"
#include "platform/Logging.h"
#include "wtf/Deque.h"
#include "wtf/text/CString.h"
namespace WebCore {
PassOwnPtr<HistoryNode> HistoryNode::create(HistoryEntry* entry, HistoryItem* value)
{
return adoptPtr(new HistoryNode(entry, value));
}
HistoryNode* HistoryNode::addChild(PassRefPtr<HistoryItem> item)
{
m_children.append(HistoryNode::create(m_entry, item.get()));
return m_children.last().get();
}
PassOwnPtr<HistoryNode> HistoryNode::cloneAndReplace(HistoryEntry* newEntry, HistoryItem* newItem, HistoryItem* oldItem, bool clipAtTarget, Frame* frame)
{
bool isNodeBeingNavigated = m_value == oldItem;
HistoryItem* itemForCreate = isNodeBeingNavigated ? newItem : m_value.get();
OwnPtr<HistoryNode> newHistoryNode = create(newEntry, itemForCreate);
if (!clipAtTarget || !isNodeBeingNavigated) {
for (Frame* child = frame->tree().firstChild(); child; child = child->tree().nextSibling()) {
HistoryNode* childHistoryNode = m_entry->m_framesToItems.get(child->frameID());
if (!childHistoryNode)
continue;
newHistoryNode->m_children.append(childHistoryNode->cloneAndReplace(newEntry, newItem, oldItem, clipAtTarget, child));
}
}
return newHistoryNode.release();
}
HistoryNode::HistoryNode(HistoryEntry* entry, HistoryItem* value)
: m_entry(entry)
, m_value(value)
{
m_entry->m_framesToItems.add(value->targetFrameID(), this);
String target = value->target();
if (target.isNull())
target = emptyString();
m_entry->m_uniqueNamesToItems.add(target, this);
}
HistoryEntry::HistoryEntry(HistoryItem* root)
{
m_root = HistoryNode::create(this, root);
}
PassOwnPtr<HistoryEntry> HistoryEntry::create(HistoryItem* root)
{
return adoptPtr(new HistoryEntry(root));
}
PassOwnPtr<HistoryEntry> HistoryEntry::cloneAndReplace(HistoryItem* newItem, HistoryItem* oldItem, bool clipAtTarget, Page* page)
{
OwnPtr<HistoryEntry> newEntry = adoptPtr(new HistoryEntry());
newEntry->m_root = m_root->cloneAndReplace(newEntry.get(), newItem, oldItem, clipAtTarget, page->mainFrame());
return newEntry.release();
}
HistoryNode* HistoryEntry::historyNodeForFrame(Frame* frame)
{
if (HistoryNode* historyNode = m_framesToItems.get(frame->frameID()))
return historyNode;
String target = frame->tree().uniqueName();
if (target.isNull())
target = emptyString();
return m_uniqueNamesToItems.get(target);
}
HistoryItem* HistoryEntry::itemForFrame(Frame* frame)
{
if (HistoryNode* historyNode = historyNodeForFrame(frame))
return historyNode->value();
return 0;
}
HistoryController::HistoryController(Page* page)
: m_page(page)
, m_defersLoading(false)
{
}
HistoryController::~HistoryController()
{
}
void HistoryController::updateBackForwardListForFragmentScroll(Frame* frame)
{
m_provisionalEntry.clear();
createNewBackForwardItem(frame, false);
}
void HistoryController::goToEntry(PassOwnPtr<HistoryEntry> targetEntry)
{
ASSERT(m_sameDocumentLoadsInProgress.isEmpty());
ASSERT(m_differentDocumentLoadsInProgress.isEmpty());
m_provisionalEntry = targetEntry;
recursiveGoToEntry(m_page->mainFrame());
if (m_differentDocumentLoadsInProgress.isEmpty()) {
m_previousEntry = m_currentEntry.release();
m_currentEntry = m_provisionalEntry.release();
} else {
m_page->mainFrame()->loader().stopAllLoaders();
}
for (HistoryFrameLoadSet::iterator it = m_sameDocumentLoadsInProgress.begin(); it != m_sameDocumentLoadsInProgress.end(); ++it)
it->key->loader().loadHistoryItem(it->value, HistorySameDocumentLoad);
for (HistoryFrameLoadSet::iterator it = m_differentDocumentLoadsInProgress.begin(); it != m_differentDocumentLoadsInProgress.end(); ++it)
it->key->loader().loadHistoryItem(it->value, HistoryDifferentDocumentLoad);
m_sameDocumentLoadsInProgress.clear();
m_differentDocumentLoadsInProgress.clear();
}
void HistoryController::recursiveGoToEntry(Frame* frame)
{
HistoryItem* newItem = m_provisionalEntry->itemForFrame(frame);
HistoryItem* oldItem = m_currentEntry ? m_currentEntry->itemForFrame(frame) : 0;
if (!newItem)
return;
if (!oldItem || (newItem != oldItem && newItem->itemSequenceNumber() != oldItem->itemSequenceNumber())) {
if (oldItem && newItem->documentSequenceNumber() == oldItem->documentSequenceNumber())
m_sameDocumentLoadsInProgress.set(frame, newItem);
else
m_differentDocumentLoadsInProgress.set(frame, newItem);
return;
}
for (Frame* child = frame->tree().firstChild(); child; child = child->tree().nextSibling())
recursiveGoToEntry(child);
}
void HistoryController::goToItem(HistoryItem* targetItem)
{
if (m_defersLoading) {
m_deferredItem = targetItem;
return;
}
OwnPtr<HistoryEntry> newEntry = HistoryEntry::create(targetItem);
Deque<HistoryNode*> historyNodes;
historyNodes.append(newEntry->rootHistoryNode());
while (!historyNodes.isEmpty()) {
// For each item, read the children (if any) off the HistoryItem,
// create a new HistoryNode for each child and attach it,
// then clear the children on the HistoryItem.
HistoryNode* historyNode = historyNodes.takeFirst();
const HistoryItemVector& children = historyNode->value()->children();
for (size_t i = 0; i < children.size(); i++) {
HistoryNode* childHistoryNode = historyNode->addChild(children[i].get());
historyNodes.append(childHistoryNode);
}
historyNode->value()->clearChildren();
}
goToEntry(newEntry.release());
}
void HistoryController::setDefersLoading(bool defer)
{
m_defersLoading = defer;
if (!defer && m_deferredItem) {
goToItem(m_deferredItem.get());
m_deferredItem = 0;
}
}
// There are 2 things you might think of as "history", all of which are handled by these functions.
//
// 1) Back/forward: The m_currentItem is part of this mechanism.
// 2) Global history: Handled by the client.
//
void HistoryController::updateForStandardLoad(Frame* frame)
{
LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", frame->loader().documentLoader()->url().string().ascii().data());
createNewBackForwardItem(frame, true);
}
void HistoryController::updateForInitialLoadInChildFrame(Frame* frame)
{
ASSERT(frame->tree().parent());
if (!m_currentEntry)
return;
if (HistoryNode* existingChildHistoryNode = m_currentEntry->historyNodeForFrame(frame))
existingChildHistoryNode->updateValue(createItem(frame));
else if (HistoryNode* parentHistoryNode = m_currentEntry->historyNodeForFrame(frame->tree().parent()))
parentHistoryNode->addChild(createItem(frame));
}
void HistoryController::updateForCommit(Frame* frame)
{
#if !LOG_DISABLED
if (frame->document())
LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frame->document()->title().utf8().data());
#endif
FrameLoadType type = frame->loader().loadType();
if (isBackForwardLoadType(type)) {
// Once committed, we want to use current item for saving DocState, and
// the provisional item for restoring state.
// Note previousItem must be set before we close the URL, which will
// happen when the data source is made non-provisional below
if (m_provisionalEntry) {
m_previousEntry = m_currentEntry.release();
ASSERT(m_provisionalEntry);
m_currentEntry = m_provisionalEntry.release();
}
frame->loader().setCurrentItem(m_currentEntry->itemForFrame(frame));
} else if (type != FrameLoadTypeRedirectWithLockedBackForwardList) {
m_provisionalEntry.clear();
}
if (type == FrameLoadTypeStandard)
updateForStandardLoad(frame);
else if (type == FrameLoadTypeInitialInChildFrame)
updateForInitialLoadInChildFrame(frame);
else
updateWithoutCreatingNewBackForwardItem(frame);
}
static PassRefPtr<HistoryItem> itemForExport(HistoryNode* historyNode)
{
RefPtr<HistoryItem> item = historyNode->value()->copy();
const Vector<OwnPtr<HistoryNode> >& childEntries = historyNode->children();
for (size_t i = 0; i < childEntries.size(); i++)
item->addChildItem(itemForExport(childEntries[i].get()));
return item;
}
PassRefPtr<HistoryItem> HistoryController::currentItemForExport(Frame* frame)
{
if (!m_currentEntry)
return 0;
HistoryNode* historyNode = m_currentEntry->historyNodeForFrame(frame);
return historyNode ? itemForExport(historyNode) : 0;
}
PassRefPtr<HistoryItem> HistoryController::previousItemForExport(Frame* frame)
{
if (!m_previousEntry)
return 0;
HistoryNode* historyNode = m_previousEntry->historyNodeForFrame(frame);
return historyNode ? itemForExport(historyNode) : 0;
}
PassRefPtr<HistoryItem> HistoryController::provisionalItemForExport(Frame* frame)
{
if (!m_provisionalEntry)
return 0;
HistoryNode* historyNode = m_provisionalEntry->historyNodeForFrame(frame);
return historyNode ? itemForExport(historyNode) : 0;
}
HistoryItem* HistoryController::itemForNewChildFrame(Frame* frame) const
{
Frame* parent = frame->tree().parent();
ASSERT(parent);
if (!m_currentEntry || !isBackForwardLoadType(parent->loader().loadType()) || parent->document()->loadEventFinished())
return 0;
return m_currentEntry->itemForFrame(frame);
}
void HistoryController::initializeItem(HistoryItem* item, Frame* frame)
{
DocumentLoader* documentLoader = 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();
}
// Frames that have never successfully loaded any content
// may have no URL at all. Currently our history code can't
// deal with such things, so we nip that in the bud here.
// Later we may want to learn to live with nil for URL.
// See bug 3368236 and related bugs for more information.
if (url.isEmpty())
url = blankURL();
if (originalURL.isEmpty())
originalURL = blankURL();
item->setURL(url);
item->setTarget(frame->tree().uniqueName());
item->setTargetFrameID(frame->frameID());
item->setOriginalURLString(originalURL.string());
// Save form state if this is a POST
item->setFormInfoFromRequest(documentLoader->request());
}
PassRefPtr<HistoryItem> HistoryController::createItem(Frame* frame)
{
RefPtr<HistoryItem> item = HistoryItem::create();
initializeItem(item.get(), frame);
frame->loader().setCurrentItem(item.get());
return item.release();
}
void HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
{
RefPtr<HistoryItem> newItem = createItem(targetFrame);
if (!m_currentEntry) {
m_currentEntry = HistoryEntry::create(newItem.get());
} else {
HistoryItem* oldItem = m_currentEntry->itemForFrame(targetFrame);
if (!clipAtTarget && oldItem)
newItem->setDocumentSequenceNumber(oldItem->documentSequenceNumber());
m_previousEntry = m_currentEntry.release();
m_currentEntry = m_previousEntry->cloneAndReplace(newItem.get(), oldItem, clipAtTarget, m_page);
}
}
void HistoryController::createNewBackForwardItem(Frame* frame, bool doClip)
{
// In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.
// The item that was the target of the user's navigation is designated as the "targetItem".
// When this function is called with doClip=true we're able to create the whole tree except for the target's children,
// which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
if (!frame->loader().documentLoader()->isURLValidForNewHistoryEntry())
return;
createItemTree(frame, doClip);
}
void HistoryController::updateWithoutCreatingNewBackForwardItem(Frame* frame)
{
if (!m_currentEntry || !m_currentEntry->itemForFrame(frame))
return;
DocumentLoader* documentLoader = frame->loader().documentLoader();
if (!documentLoader->unreachableURL().isEmpty())
return;
HistoryItem* item = m_currentEntry->itemForFrame(frame);
if (item->url() != documentLoader->url()) {
item->reset();
initializeItem(item, frame);
} else {
// Even if the final URL didn't change, the form data may have changed.
item->setFormInfoFromRequest(documentLoader->request());
}
}
} // namespace WebCore