| /* |
| * 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 "HistoryController.h" |
| |
| #include "BackForwardList.h" |
| #include "CachedPage.h" |
| #include "CString.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" |
| |
| 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()); |
| // FIXME: It would be great to work out a way to put this code in WebCore instead of calling through to the client. |
| m_frame->loader()->client()->saveViewStateToItem(item); |
| } |
| |
| /* |
| There is a race condition between the layout and load completion that affects restoring the scroll position. |
| We try to restore the scroll position at both the first layout and upon load completion. |
| |
| 1) If first layout happens before the load completes, we want to restore the scroll position then so that the |
| first time we draw the page is already scrolled to the right place, instead of starting at the top and later |
| jumping down. It is possible that the old scroll position is past the part of the doc laid out so far, in |
| which case the restore silent fails and we will fix it in when we try to restore on doc completion. |
| 2) If the layout happens after the load completes, the attempt to restore at load completion time silently |
| fails. We then successfully restore it when the layout happens. |
| */ |
| void HistoryController::restoreScrollPositionAndViewState() |
| { |
| if (!m_frame->loader()->committedFirstRealDocumentLoad()) |
| return; |
| |
| ASSERT(m_currentItem); |
| |
| // FIXME: As the ASSERT attests, it seems we should always have a currentItem here. |
| // One counterexample is <rdar://problem/4917290> |
| // For now, to cover this issue in release builds, there is no technical harm to returning |
| // early and from a user standpoint - as in the above radar - the previous page load failed |
| // so there *is* no scroll or view state to restore! |
| if (!m_currentItem) |
| return; |
| |
| // FIXME: It would be great to work out a way to put this code in WebCore instead of calling |
| // through to the client. It's currently used only for the PDF view on Mac. |
| m_frame->loader()->client()->restoreViewState(); |
| |
| if (FrameView* view = m_frame->view()) |
| if (!view->wasScrolledByUser()) |
| view->setScrollPosition(m_currentItem->scrollPoint()); |
| } |
| |
| void HistoryController::updateBackForwardListForFragmentScroll() |
| { |
| updateBackForwardListClippedAtTarget(false); |
| |
| // Since the document isn't changed as a result of a fragment scroll, we should |
| // preserve the DocumentSequenceNumber of the previous item. |
| m_currentItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber()); |
| } |
| |
| void HistoryController::saveDocumentState() |
| { |
| // FIXME: Reading this bit of FrameLoader state here is unfortunate. I need to study |
| // this more to see if we can remove this dependency. |
| if (m_frame->loader()->creatingInitialEmptyDocument()) |
| return; |
| |
| // For a standard page load, we will have a previous item set, which will be used to |
| // store the form state. However, in some cases we will have no previous item, and |
| // the current item is the right place to save the state. One example is when we |
| // detach a bunch of frames because we are navigating from a site with frames to |
| // another site. Another is when saving the frame state of a frame that is not the |
| // target of the current navigation (if we even decide to save with that granularity). |
| |
| // Because of previousItem's "masking" of currentItem for this purpose, it's important |
| // that previousItem be cleared at the end of a page transition. We leverage the |
| // checkLoadComplete recursion to achieve this goal. |
| |
| 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()); |
| } |
| } |
| |
| // Walk the frame tree, telling all frames to save their form state into their current |
| // history item. |
| 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() |
| { |
| // When we are pre-commit, the currentItem is where the pageCache data resides |
| CachedPage* cachedPage = pageCache()->get(currentItem()); |
| |
| // FIXME: This is a grotesque hack to fix <rdar://problem/4059059> Crash in RenderFlow::detach |
| // Somehow the PageState object is not properly updated, and is holding onto a stale document. |
| // Both Xcode and FileMaker see this crash, Safari does not. |
| |
| 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()); |
| } |
| |
| // Main funnel for navigating to a previous location (back/forward, non-search snap-back) |
| // This includes recursion to handle loading into framesets properly |
| void HistoryController::goToItem(HistoryItem* targetItem, FrameLoadType type) |
| { |
| ASSERT(!m_frame->tree()->parent()); |
| |
| // shouldGoToHistoryItem is a private delegate method. This is needed to fix: |
| // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls |
| // Ultimately, history item navigations should go through the policy delegate. That's covered in: |
| // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate |
| Page* page = m_frame->page(); |
| if (!page) |
| return; |
| if (!m_frame->loader()->client()->shouldGoToHistoryItem(targetItem)) |
| return; |
| |
| // Set the BF cursor before commit, which lets the user quickly click back/forward again. |
| // - plus, it only makes sense for the top level of the operation through the frametree, |
| // as opposed to happening for some/one of the page commits that might happen soon |
| 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); |
| } |
| |
| // Walk the frame tree and ensure that the URLs match the URLs in the item. |
| 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 |
| |
| // Must grab the current scroll position before disturbing it |
| 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()); |
| |
| // Sometimes loading a page again leads to a different result because of cookies. Bugzilla 4072 |
| if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty()) |
| m_currentItem->setURL(m_frame->loader()->documentLoader()->requestURL()); |
| } |
| } |
| |
| // There are 3 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. |
| // 3) Visited links: Handled by the PageGroup. |
| |
| 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 |
| |
| // Clear out form data so we don't try to restore it into the incoming page. Must happen after |
| // webcore has closed the URL and saved away the form state. |
| 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())) { |
| // 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 |
| 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() |
| { |
| // Even if already complete, we might have set a previous item on a frame that |
| // didn't do any data loading on the past transaction. Make sure to clear these out. |
| m_previousItem = 0; |
| } |
| |
| void HistoryController::setCurrentItem(HistoryItem* item) |
| { |
| m_currentItem = item; |
| } |
| |
| void HistoryController::setCurrentItemTitle(const String& title) |
| { |
| if (m_currentItem) |
| m_currentItem->setTitle(title); |
| } |
| |
| 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()); |
| |
| // 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(); |
| |
| 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); |
| |
| // Save form state if this is a POST |
| if (docLoader) { |
| if (useOriginal) |
| item->setFormInfoFromRequest(docLoader->originalRequest()); |
| else |
| item->setFormInfoFromRequest(docLoader->request()); |
| } |
| |
| // Set the item for which we will save document state |
| 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)) { |
| // save frame state for items that aren't loading (khtml doesn't save those) |
| saveDocumentState(); |
| for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) { |
| FrameLoader* childLoader = child->loader(); |
| bool hasChildLoaded = childLoader->frameHasLoaded(); |
| |
| // If the child is a frame corresponding to an <object> element that never loaded, |
| // we don't want to create a history item, because that causes fallback content |
| // to be ignored on reload. |
| |
| if (!(!hasChildLoaded && childLoader->isHostedByObjectElement())) |
| bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget)); |
| } |
| } |
| if (m_frame == targetFrame) |
| bfItem->setIsTargetItem(true); |
| return bfItem; |
| } |
| |
| // The general idea here is to traverse the frame tree and the item tree in parallel, |
| // tracking whether each frame already has the content the item requests. If there is |
| // a match (by URL), we just restore scroll position and recurse. Otherwise we must |
| // reload that frame, and all its kids. |
| 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(); |
| |
| // Always reload the target frame of the item we're going to. This ensures that we will |
| // do -some- load for the transition, which means a proper notification will be posted |
| // to the app. |
| // The exact URL has to match, including fragment. We want to go through the _load |
| // method, even if to do a within-page navigation. |
| // The current frame tree and the frame tree snapshot in the item have to match. |
| if (!item->isTargetItem() && |
| itemURL == currentURL && |
| ((m_frame->tree()->name().isEmpty() && item->target().isEmpty()) || m_frame->tree()->name() == item->target()) && |
| childFramesMatchItem(item)) |
| { |
| // This content is good, so leave it alone and look for children that need reloading |
| // Save form state (works from currentItem, since prevItem is nil) |
| ASSERT(!m_previousItem); |
| saveDocumentState(); |
| saveScrollPositionAndViewStateToItem(m_currentItem.get()); |
| |
| if (FrameView* view = m_frame->view()) |
| view->setWasScrolledByUser(false); |
| |
| m_currentItem = item; |
| |
| // Restore form state (works from currentItem) |
| restoreDocumentState(); |
| |
| // Restore the scroll position (we choose to do this rather than going back to the anchor point) |
| 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); |
| } |
| } |
| |
| // helper method that determines whether the subframes described by the item's subitems |
| // match our own current frameset |
| 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; |
| } |
| |
| // Found matches for all item targets |
| return true; |
| } |
| |
| void HistoryController::updateBackForwardListClippedAtTarget(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. |
| |
| 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); |
| |
| // Get a HistoryItem tree for the current frame tree. |
| RefPtr<HistoryItem> item = createItemTree(m_frame, false); |
| ASSERT(item->isTargetItem()); |
| |
| // Override data in the target item to reflect the pushState() arguments. |
| item->setTitle(title); |
| item->setStateObject(stateObject); |
| item->setURLString(urlString); |
| |
| // Since the document isn't changed as a result of a pushState call, we |
| // should preserve the DocumentSequenceNumber of the previous item. |
| item->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber()); |
| |
| page->backForwardList()->pushStateItem(item.release()); |
| } |
| |
| void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString) |
| { |
| Page* page = m_frame->page(); |
| ASSERT(page); |
| HistoryItem* current = page->backForwardList()->currentItem(); |
| ASSERT(current); |
| |
| if (!urlString.isEmpty()) |
| current->setURLString(urlString); |
| current->setTitle(title); |
| current->setStateObject(stateObject); |
| } |
| |
| } // namespace WebCore |