WebCore/loader/HistoryController.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2  * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
       
     3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
       
     4  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
       
     5  *
       
     6  * Redistribution and use in source and binary forms, with or without
       
     7  * modification, are permitted provided that the following conditions
       
     8  * are met:
       
     9  *
       
    10  * 1.  Redistributions of source code must retain the above copyright
       
    11  *     notice, this list of conditions and the following disclaimer. 
       
    12  * 2.  Redistributions in binary form must reproduce the above copyright
       
    13  *     notice, this list of conditions and the following disclaimer in the
       
    14  *     documentation and/or other materials provided with the distribution. 
       
    15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
       
    16  *     its contributors may be used to endorse or promote products derived
       
    17  *     from this software without specific prior written permission. 
       
    18  *
       
    19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
       
    20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
    21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       
    22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
       
    23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       
    24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
       
    25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
       
    26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
       
    28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    29  */
       
    30 
       
    31 #include "config.h"
       
    32 #include "HistoryController.h"
       
    33 
       
    34 #include "BackForwardList.h"
       
    35 #include "CachedPage.h"
       
    36 #include "DocumentLoader.h"
       
    37 #include "Frame.h"
       
    38 #include "FrameLoader.h"
       
    39 #include "FrameLoaderClient.h"
       
    40 #include "FrameLoaderStateMachine.h"
       
    41 #include "FrameTree.h"
       
    42 #include "FrameView.h"
       
    43 #include "HistoryItem.h"
       
    44 #include "Logging.h"
       
    45 #include "Page.h"
       
    46 #include "PageCache.h"
       
    47 #include "PageGroup.h"
       
    48 #include "Settings.h"
       
    49 #include <wtf/text/CString.h>
       
    50 
       
    51 namespace WebCore {
       
    52 
       
    53 HistoryController::HistoryController(Frame* frame)
       
    54     : m_frame(frame)
       
    55 {
       
    56 }
       
    57 
       
    58 HistoryController::~HistoryController()
       
    59 {
       
    60 }
       
    61 
       
    62 void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
       
    63 {
       
    64     if (!item || !m_frame->view())
       
    65         return;
       
    66         
       
    67     item->setScrollPoint(m_frame->view()->scrollPosition());
       
    68     // FIXME: It would be great to work out a way to put this code in WebCore instead of calling through to the client.
       
    69     m_frame->loader()->client()->saveViewStateToItem(item);
       
    70 }
       
    71 
       
    72 /*
       
    73  There is a race condition between the layout and load completion that affects restoring the scroll position.
       
    74  We try to restore the scroll position at both the first layout and upon load completion.
       
    75  
       
    76  1) If first layout happens before the load completes, we want to restore the scroll position then so that the
       
    77  first time we draw the page is already scrolled to the right place, instead of starting at the top and later
       
    78  jumping down.  It is possible that the old scroll position is past the part of the doc laid out so far, in
       
    79  which case the restore silent fails and we will fix it in when we try to restore on doc completion.
       
    80  2) If the layout happens after the load completes, the attempt to restore at load completion time silently
       
    81  fails.  We then successfully restore it when the layout happens.
       
    82 */
       
    83 void HistoryController::restoreScrollPositionAndViewState()
       
    84 {
       
    85     if (!m_frame->loader()->stateMachine()->committedFirstRealDocumentLoad())
       
    86         return;
       
    87 
       
    88     ASSERT(m_currentItem);
       
    89     
       
    90     // FIXME: As the ASSERT attests, it seems we should always have a currentItem here.
       
    91     // One counterexample is <rdar://problem/4917290>
       
    92     // For now, to cover this issue in release builds, there is no technical harm to returning
       
    93     // early and from a user standpoint - as in the above radar - the previous page load failed 
       
    94     // so there *is* no scroll or view state to restore!
       
    95     if (!m_currentItem)
       
    96         return;
       
    97     
       
    98     // FIXME: It would be great to work out a way to put this code in WebCore instead of calling
       
    99     // through to the client. It's currently used only for the PDF view on Mac.
       
   100     m_frame->loader()->client()->restoreViewState();
       
   101     
       
   102     if (FrameView* view = m_frame->view())
       
   103         if (!view->wasScrolledByUser())
       
   104             view->setScrollPosition(m_currentItem->scrollPoint());
       
   105 }
       
   106 
       
   107 void HistoryController::updateBackForwardListForFragmentScroll()
       
   108 {
       
   109     updateBackForwardListClippedAtTarget(false);
       
   110 }
       
   111 
       
   112 void HistoryController::saveDocumentState()
       
   113 {
       
   114     // FIXME: Reading this bit of FrameLoader state here is unfortunate.  I need to study
       
   115     // this more to see if we can remove this dependency.
       
   116     if (m_frame->loader()->stateMachine()->creatingInitialEmptyDocument())
       
   117         return;
       
   118 
       
   119     // For a standard page load, we will have a previous item set, which will be used to
       
   120     // store the form state.  However, in some cases we will have no previous item, and
       
   121     // the current item is the right place to save the state.  One example is when we
       
   122     // detach a bunch of frames because we are navigating from a site with frames to
       
   123     // another site.  Another is when saving the frame state of a frame that is not the
       
   124     // target of the current navigation (if we even decide to save with that granularity).
       
   125 
       
   126     // Because of previousItem's "masking" of currentItem for this purpose, it's important
       
   127     // that previousItem be cleared at the end of a page transition.  We leverage the
       
   128     // checkLoadComplete recursion to achieve this goal.
       
   129 
       
   130     HistoryItem* item = m_previousItem ? m_previousItem.get() : m_currentItem.get();
       
   131     if (!item)
       
   132         return;
       
   133 
       
   134     Document* document = m_frame->document();
       
   135     ASSERT(document);
       
   136     
       
   137     if (item->isCurrentDocument(document)) {
       
   138         LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame->tree()->name().string().utf8().data(), item);
       
   139         item->setDocumentState(document->formElementsState());
       
   140     }
       
   141 }
       
   142 
       
   143 // Walk the frame tree, telling all frames to save their form state into their current
       
   144 // history item.
       
   145 void HistoryController::saveDocumentAndScrollState()
       
   146 {
       
   147     for (Frame* frame = m_frame; frame; frame = frame->tree()->traverseNext(m_frame)) {
       
   148         frame->loader()->history()->saveDocumentState();
       
   149         frame->loader()->history()->saveScrollPositionAndViewStateToItem(frame->loader()->history()->currentItem());
       
   150     }
       
   151 }
       
   152 
       
   153 void HistoryController::restoreDocumentState()
       
   154 {
       
   155     Document* doc = m_frame->document();
       
   156         
       
   157     HistoryItem* itemToRestore = 0;
       
   158     
       
   159     switch (m_frame->loader()->loadType()) {
       
   160         case FrameLoadTypeReload:
       
   161         case FrameLoadTypeReloadFromOrigin:
       
   162         case FrameLoadTypeSame:
       
   163         case FrameLoadTypeReplace:
       
   164             break;
       
   165         case FrameLoadTypeBack:
       
   166         case FrameLoadTypeBackWMLDeckNotAccessible:
       
   167         case FrameLoadTypeForward:
       
   168         case FrameLoadTypeIndexedBackForward:
       
   169         case FrameLoadTypeRedirectWithLockedBackForwardList:
       
   170         case FrameLoadTypeStandard:
       
   171             itemToRestore = m_currentItem.get(); 
       
   172     }
       
   173     
       
   174     if (!itemToRestore)
       
   175         return;
       
   176 
       
   177     LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame->tree()->name().string().utf8().data(), itemToRestore);
       
   178     doc->setStateForNewFormElements(itemToRestore->documentState());
       
   179 }
       
   180 
       
   181 void HistoryController::invalidateCurrentItemCachedPage()
       
   182 {
       
   183     // When we are pre-commit, the currentItem is where the pageCache data resides    
       
   184     CachedPage* cachedPage = pageCache()->get(currentItem());
       
   185 
       
   186     // FIXME: This is a grotesque hack to fix <rdar://problem/4059059> Crash in RenderFlow::detach
       
   187     // Somehow the PageState object is not properly updated, and is holding onto a stale document.
       
   188     // Both Xcode and FileMaker see this crash, Safari does not.
       
   189     
       
   190     ASSERT(!cachedPage || cachedPage->document() == m_frame->document());
       
   191     if (cachedPage && cachedPage->document() == m_frame->document()) {
       
   192         cachedPage->document()->setInPageCache(false);
       
   193         cachedPage->clear();
       
   194     }
       
   195     
       
   196     if (cachedPage)
       
   197         pageCache()->remove(currentItem());
       
   198 }
       
   199 
       
   200 // Main funnel for navigating to a previous location (back/forward, non-search snap-back)
       
   201 // This includes recursion to handle loading into framesets properly
       
   202 void HistoryController::goToItem(HistoryItem* targetItem, FrameLoadType type)
       
   203 {
       
   204     ASSERT(!m_frame->tree()->parent());
       
   205     
       
   206     // shouldGoToHistoryItem is a private delegate method. This is needed to fix:
       
   207     // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls
       
   208     // Ultimately, history item navigations should go through the policy delegate. That's covered in:
       
   209     // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate
       
   210     Page* page = m_frame->page();
       
   211     if (!page)
       
   212         return;
       
   213     if (!m_frame->loader()->client()->shouldGoToHistoryItem(targetItem))
       
   214         return;
       
   215 
       
   216     // Set the BF cursor before commit, which lets the user quickly click back/forward again.
       
   217     // - plus, it only makes sense for the top level of the operation through the frametree,
       
   218     // as opposed to happening for some/one of the page commits that might happen soon
       
   219     BackForwardList* bfList = page->backForwardList();
       
   220     HistoryItem* currentItem = bfList->currentItem();    
       
   221     bfList->goToItem(targetItem);
       
   222     Settings* settings = m_frame->settings();
       
   223     page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : targetItem);
       
   224     recursiveGoToItem(targetItem, currentItem, type);
       
   225 }
       
   226 
       
   227 void HistoryController::updateForBackForwardNavigation()
       
   228 {
       
   229 #if !LOG_DISABLED
       
   230     if (m_frame->loader()->documentLoader())
       
   231         LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
       
   232 #endif
       
   233 
       
   234     // Must grab the current scroll position before disturbing it
       
   235     saveScrollPositionAndViewStateToItem(m_previousItem.get());
       
   236 }
       
   237 
       
   238 void HistoryController::updateForReload()
       
   239 {
       
   240 #if !LOG_DISABLED
       
   241     if (m_frame->loader()->documentLoader())
       
   242         LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
       
   243 #endif
       
   244 
       
   245     if (m_currentItem) {
       
   246         pageCache()->remove(m_currentItem.get());
       
   247     
       
   248         if (m_frame->loader()->loadType() == FrameLoadTypeReload || m_frame->loader()->loadType() == FrameLoadTypeReloadFromOrigin)
       
   249             saveScrollPositionAndViewStateToItem(m_currentItem.get());
       
   250     
       
   251         // Sometimes loading a page again leads to a different result because of cookies. Bugzilla 4072
       
   252         if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty())
       
   253             m_currentItem->setURL(m_frame->loader()->documentLoader()->requestURL());
       
   254     }
       
   255 }
       
   256 
       
   257 // There are 3 things you might think of as "history", all of which are handled by these functions.
       
   258 //
       
   259 //     1) Back/forward: The m_currentItem is part of this mechanism.
       
   260 //     2) Global history: Handled by the client.
       
   261 //     3) Visited links: Handled by the PageGroup.
       
   262 
       
   263 void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
       
   264 {
       
   265     LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data());
       
   266 
       
   267     FrameLoader* frameLoader = m_frame->loader();
       
   268 
       
   269     Settings* settings = m_frame->settings();
       
   270     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
       
   271     const KURL& historyURL = frameLoader->documentLoader()->urlForHistory();
       
   272 
       
   273     if (!frameLoader->documentLoader()->isClientRedirect()) {
       
   274         if (!historyURL.isEmpty()) {
       
   275             if (updateType != UpdateAllExceptBackForwardList)
       
   276                 updateBackForwardListClippedAtTarget(true);
       
   277             if (!needPrivacy) {
       
   278                 frameLoader->client()->updateGlobalHistory();
       
   279                 frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true);
       
   280                 if (frameLoader->documentLoader()->unreachableURL().isEmpty())
       
   281                     frameLoader->client()->updateGlobalHistoryRedirectLinks();
       
   282             }
       
   283             if (Page* page = m_frame->page())
       
   284                 page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForwardList()->currentItem());
       
   285         }
       
   286     } else if (frameLoader->documentLoader()->unreachableURL().isEmpty() && m_currentItem) {
       
   287         m_currentItem->setURL(frameLoader->documentLoader()->url());
       
   288         m_currentItem->setFormInfoFromRequest(frameLoader->documentLoader()->request());
       
   289     }
       
   290 
       
   291     if (!historyURL.isEmpty() && !needPrivacy) {
       
   292         if (Page* page = m_frame->page())
       
   293             page->group().addVisitedLink(historyURL);
       
   294 
       
   295         if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !frameLoader->url().isEmpty())
       
   296             frameLoader->client()->updateGlobalHistoryRedirectLinks();
       
   297     }
       
   298 }
       
   299 
       
   300 void HistoryController::updateForRedirectWithLockedBackForwardList()
       
   301 {
       
   302 #if !LOG_DISABLED
       
   303     if (m_frame->loader()->documentLoader())
       
   304         LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
       
   305 #endif
       
   306     
       
   307     Settings* settings = m_frame->settings();
       
   308     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
       
   309     const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
       
   310 
       
   311     if (m_frame->loader()->documentLoader()->isClientRedirect()) {
       
   312         if (!m_currentItem && !m_frame->tree()->parent()) {
       
   313             if (!historyURL.isEmpty()) {
       
   314                 updateBackForwardListClippedAtTarget(true);
       
   315                 if (!needPrivacy) {
       
   316                     m_frame->loader()->client()->updateGlobalHistory();
       
   317                     m_frame->loader()->documentLoader()->setDidCreateGlobalHistoryEntry(true);
       
   318                     if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty())
       
   319                         m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
       
   320                 }
       
   321                 if (Page* page = m_frame->page())
       
   322                     page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForwardList()->currentItem());
       
   323             }
       
   324         }
       
   325         if (m_currentItem) {
       
   326             m_currentItem->setURL(m_frame->loader()->documentLoader()->url());
       
   327             m_currentItem->setFormInfoFromRequest(m_frame->loader()->documentLoader()->request());
       
   328         }
       
   329     } else {
       
   330         Frame* parentFrame = m_frame->tree()->parent();
       
   331         if (parentFrame && parentFrame->loader()->history()->m_currentItem)
       
   332             parentFrame->loader()->history()->m_currentItem->setChildItem(createItem(true));
       
   333     }
       
   334 
       
   335     if (!historyURL.isEmpty() && !needPrivacy) {
       
   336         if (Page* page = m_frame->page())
       
   337             page->group().addVisitedLink(historyURL);
       
   338 
       
   339         if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->loader()->url().isEmpty())
       
   340             m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
       
   341     }
       
   342 }
       
   343 
       
   344 void HistoryController::updateForClientRedirect()
       
   345 {
       
   346 #if !LOG_DISABLED
       
   347     if (m_frame->loader()->documentLoader())
       
   348         LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
       
   349 #endif
       
   350 
       
   351     // Clear out form data so we don't try to restore it into the incoming page.  Must happen after
       
   352     // webcore has closed the URL and saved away the form state.
       
   353     if (m_currentItem) {
       
   354         m_currentItem->clearDocumentState();
       
   355         m_currentItem->clearScrollPoint();
       
   356     }
       
   357 
       
   358     Settings* settings = m_frame->settings();
       
   359     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
       
   360     const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
       
   361 
       
   362     if (!historyURL.isEmpty() && !needPrivacy) {
       
   363         if (Page* page = m_frame->page())
       
   364             page->group().addVisitedLink(historyURL);
       
   365     }
       
   366 }
       
   367 
       
   368 void HistoryController::updateForCommit()
       
   369 {
       
   370     FrameLoader* frameLoader = m_frame->loader();
       
   371 #if !LOG_DISABLED
       
   372     if (frameLoader->documentLoader())
       
   373         LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().utf8().data());
       
   374 #endif
       
   375     FrameLoadType type = frameLoader->loadType();
       
   376     if (isBackForwardLoadType(type) ||
       
   377         ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) {
       
   378         // Once committed, we want to use current item for saving DocState, and
       
   379         // the provisional item for restoring state.
       
   380         // Note previousItem must be set before we close the URL, which will
       
   381         // happen when the data source is made non-provisional below
       
   382         m_previousItem = m_currentItem;
       
   383         ASSERT(m_provisionalItem);
       
   384         m_currentItem = m_provisionalItem;
       
   385         m_provisionalItem = 0;
       
   386     }
       
   387 }
       
   388 
       
   389 void HistoryController::updateForSameDocumentNavigation()
       
   390 {
       
   391     if (m_frame->loader()->url().isEmpty())
       
   392         return;
       
   393 
       
   394     Settings* settings = m_frame->settings();
       
   395     if (!settings || settings->privateBrowsingEnabled())
       
   396         return;
       
   397 
       
   398     Page* page = m_frame->page();
       
   399     if (!page)
       
   400         return;
       
   401 
       
   402     page->group().addVisitedLink(m_frame->loader()->url());
       
   403 }
       
   404 
       
   405 void HistoryController::updateForFrameLoadCompleted()
       
   406 {
       
   407     // Even if already complete, we might have set a previous item on a frame that
       
   408     // didn't do any data loading on the past transaction. Make sure to clear these out.
       
   409     m_previousItem = 0;
       
   410 }
       
   411 
       
   412 void HistoryController::setCurrentItem(HistoryItem* item)
       
   413 {
       
   414     m_currentItem = item;
       
   415 }
       
   416 
       
   417 void HistoryController::setCurrentItemTitle(const String& title)
       
   418 {
       
   419     if (m_currentItem)
       
   420         m_currentItem->setTitle(title);
       
   421 }
       
   422 
       
   423 bool HistoryController::currentItemShouldBeReplaced() const
       
   424 {
       
   425     // From the HTML5 spec for location.assign():
       
   426     //  "If the browsing context's session history contains only one Document,
       
   427     //   and that was the about:blank Document created when the browsing context
       
   428     //   was created, then the navigation must be done with replacement enabled."
       
   429     return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
       
   430 }
       
   431 
       
   432 void HistoryController::setProvisionalItem(HistoryItem* item)
       
   433 {
       
   434     m_provisionalItem = item;
       
   435 }
       
   436 
       
   437 PassRefPtr<HistoryItem> HistoryController::createItem(bool useOriginal)
       
   438 {
       
   439     DocumentLoader* docLoader = m_frame->loader()->documentLoader();
       
   440     
       
   441     KURL unreachableURL = docLoader ? docLoader->unreachableURL() : KURL();
       
   442     
       
   443     KURL url;
       
   444     KURL originalURL;
       
   445 
       
   446     if (!unreachableURL.isEmpty()) {
       
   447         url = unreachableURL;
       
   448         originalURL = unreachableURL;
       
   449     } else {
       
   450         originalURL = docLoader ? docLoader->originalURL() : KURL();
       
   451         if (useOriginal)
       
   452             url = originalURL;
       
   453         else if (docLoader)
       
   454             url = docLoader->requestURL();
       
   455     }
       
   456 
       
   457     LOG(History, "WebCoreHistory: Creating item for %s", url.string().ascii().data());
       
   458     
       
   459     // Frames that have never successfully loaded any content
       
   460     // may have no URL at all. Currently our history code can't
       
   461     // deal with such things, so we nip that in the bud here.
       
   462     // Later we may want to learn to live with nil for URL.
       
   463     // See bug 3368236 and related bugs for more information.
       
   464     if (url.isEmpty()) 
       
   465         url = blankURL();
       
   466     if (originalURL.isEmpty())
       
   467         originalURL = blankURL();
       
   468     
       
   469     Frame* parentFrame = m_frame->tree()->parent();
       
   470     String parent = parentFrame ? parentFrame->tree()->name() : "";
       
   471     String title = docLoader ? docLoader->title() : "";
       
   472 
       
   473     RefPtr<HistoryItem> item = HistoryItem::create(url, m_frame->tree()->name(), parent, title);
       
   474     item->setOriginalURLString(originalURL.string());
       
   475 
       
   476     if (!unreachableURL.isEmpty() || !docLoader || docLoader->response().httpStatusCode() >= 400)
       
   477         item->setLastVisitWasFailure(true);
       
   478 
       
   479     // Save form state if this is a POST
       
   480     if (docLoader) {
       
   481         if (useOriginal)
       
   482             item->setFormInfoFromRequest(docLoader->originalRequest());
       
   483         else
       
   484             item->setFormInfoFromRequest(docLoader->request());
       
   485     }
       
   486     
       
   487     // Set the item for which we will save document state
       
   488     m_previousItem = m_currentItem;
       
   489     m_currentItem = item;
       
   490     
       
   491     return item.release();
       
   492 }
       
   493 
       
   494 PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
       
   495 {
       
   496     RefPtr<HistoryItem> bfItem = createItem(m_frame->tree()->parent() ? true : false);
       
   497     if (m_previousItem)
       
   498         saveScrollPositionAndViewStateToItem(m_previousItem.get());
       
   499 
       
   500     if (!clipAtTarget || m_frame != targetFrame) {
       
   501         // save frame state for items that aren't loading (khtml doesn't save those)
       
   502         saveDocumentState();
       
   503 
       
   504         // clipAtTarget is false for navigations within the same document, so
       
   505         // we should copy the documentSequenceNumber over to the newly create
       
   506         // item.  Non-target items are just clones, and they should therefore
       
   507         // preserve the same itemSequenceNumber.
       
   508         if (m_previousItem) {
       
   509             if (m_frame != targetFrame)
       
   510                 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
       
   511             bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
       
   512         }
       
   513 
       
   514         for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
       
   515             FrameLoader* childLoader = child->loader();
       
   516             bool hasChildLoaded = childLoader->frameHasLoaded();
       
   517 
       
   518             // If the child is a frame corresponding to an <object> element that never loaded,
       
   519             // we don't want to create a history item, because that causes fallback content
       
   520             // to be ignored on reload.
       
   521             
       
   522             if (!(!hasChildLoaded && childLoader->isHostedByObjectElement()))
       
   523                 bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget));
       
   524         }
       
   525     }
       
   526     // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
       
   527     if (m_frame == targetFrame)
       
   528         bfItem->setIsTargetItem(true);
       
   529     return bfItem;
       
   530 }
       
   531 
       
   532 // The general idea here is to traverse the frame tree and the item tree in parallel,
       
   533 // tracking whether each frame already has the content the item requests.  If there is
       
   534 // a match (by URL), we just restore scroll position and recurse.  Otherwise we must
       
   535 // reload that frame, and all its kids.
       
   536 void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
       
   537 {
       
   538     ASSERT(item);
       
   539     ASSERT(fromItem);
       
   540 
       
   541     // If the item we're going to is a clone of the item we're at, then do
       
   542     // not load it again, and continue history traversal to its children.
       
   543     // The current frame tree and the frame tree snapshot in the item have
       
   544     // to match.
       
   545     // Note: If item and fromItem are the same, then we need to create a new
       
   546     // document.
       
   547     if (item != fromItem && item->itemSequenceNumber() == fromItem->itemSequenceNumber()
       
   548         && ((m_frame->tree()->name().isEmpty() && item->target().isEmpty()) || m_frame->tree()->name() == item->target())
       
   549         && childFramesMatchItem(item))
       
   550     {
       
   551         // This content is good, so leave it alone and look for children that need reloading
       
   552         // Save form state (works from currentItem, since prevItem is nil)
       
   553         ASSERT(!m_previousItem);
       
   554         saveDocumentState();
       
   555         saveScrollPositionAndViewStateToItem(m_currentItem.get());
       
   556 
       
   557         if (FrameView* view = m_frame->view())
       
   558             view->setWasScrolledByUser(false);
       
   559 
       
   560         m_currentItem = item;
       
   561                 
       
   562         // Restore form state (works from currentItem)
       
   563         restoreDocumentState();
       
   564         
       
   565         // Restore the scroll position (we choose to do this rather than going back to the anchor point)
       
   566         restoreScrollPositionAndViewState();
       
   567         
       
   568         const HistoryItemVector& childItems = item->children();
       
   569         
       
   570         int size = childItems.size();
       
   571         for (int i = 0; i < size; ++i) {
       
   572             String childFrameName = childItems[i]->target();
       
   573             HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
       
   574             ASSERT(fromChildItem || fromItem->isTargetItem());
       
   575             Frame* childFrame = m_frame->tree()->child(childFrameName);
       
   576             ASSERT(childFrame);
       
   577             childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type);
       
   578         }
       
   579     } else {
       
   580         m_frame->loader()->loadItem(item, type);
       
   581     }
       
   582 }
       
   583 
       
   584 // helper method that determines whether the subframes described by the item's subitems
       
   585 // match our own current frameset
       
   586 bool HistoryController::childFramesMatchItem(HistoryItem* item) const
       
   587 {
       
   588     const HistoryItemVector& childItems = item->children();
       
   589     if (childItems.size() != m_frame->tree()->childCount())
       
   590         return false;
       
   591     
       
   592     unsigned size = childItems.size();
       
   593     for (unsigned i = 0; i < size; ++i) {
       
   594         if (!m_frame->tree()->child(childItems[i]->target()))
       
   595             return false;
       
   596     }
       
   597     
       
   598     // Found matches for all item targets
       
   599     return true;
       
   600 }
       
   601 
       
   602 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
       
   603 {
       
   604     // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.  
       
   605     // The item that was the target of the user's navigation is designated as the "targetItem".  
       
   606     // When this function is called with doClip=true we're able to create the whole tree except for the target's children, 
       
   607     // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
       
   608 
       
   609     Page* page = m_frame->page();
       
   610     if (!page)
       
   611         return;
       
   612 
       
   613     if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
       
   614         return;
       
   615 
       
   616     Frame* mainFrame = page->mainFrame();
       
   617     ASSERT(mainFrame);
       
   618     FrameLoader* frameLoader = mainFrame->loader();
       
   619 
       
   620     frameLoader->checkDidPerformFirstNavigation();
       
   621 
       
   622     RefPtr<HistoryItem> item = frameLoader->history()->createItemTree(m_frame, doClip);
       
   623     LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", item.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
       
   624     page->backForwardList()->addItem(item);
       
   625 }
       
   626 
       
   627 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
       
   628 {
       
   629     if (!m_currentItem)
       
   630         return;
       
   631 
       
   632     Page* page = m_frame->page();
       
   633     ASSERT(page);
       
   634 
       
   635     // Get a HistoryItem tree for the current frame tree.
       
   636     RefPtr<HistoryItem> item = createItemTree(m_frame, false);
       
   637     ASSERT(item->isTargetItem());
       
   638     
       
   639     // Override data in the target item to reflect the pushState() arguments.
       
   640     item->setTitle(title);
       
   641     item->setStateObject(stateObject);
       
   642     item->setURLString(urlString);
       
   643 
       
   644     page->backForwardList()->pushStateItem(item.release());
       
   645 }
       
   646 
       
   647 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
       
   648 {
       
   649     if (!m_currentItem)
       
   650         return;
       
   651 
       
   652     if (!urlString.isEmpty())
       
   653         m_currentItem->setURLString(urlString);
       
   654     m_currentItem->setTitle(title);
       
   655     m_currentItem->setStateObject(stateObject);
       
   656 }
       
   657 
       
   658 } // namespace WebCore