Home | History | Annotate | Download | only in loader
      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 "core/loader/HistoryController.h"
     33 
     34 #include "core/dom/Document.h"
     35 #include "core/history/BackForwardController.h"
     36 #include "core/history/HistoryItem.h"
     37 #include "core/loader/DocumentLoader.h"
     38 #include "core/loader/FrameLoader.h"
     39 #include "core/loader/FrameLoaderClient.h"
     40 #include "core/loader/FrameLoaderStateMachine.h"
     41 #include "core/page/Frame.h"
     42 #include "core/page/FrameTree.h"
     43 #include "core/page/FrameView.h"
     44 #include "core/page/Page.h"
     45 #include "core/page/scrolling/ScrollingCoordinator.h"
     46 #include "core/platform/Logging.h"
     47 #include "wtf/text/CString.h"
     48 
     49 namespace WebCore {
     50 
     51 HistoryController::HistoryController(Frame* frame)
     52     : m_frame(frame)
     53     , m_defersLoading(false)
     54 {
     55 }
     56 
     57 HistoryController::~HistoryController()
     58 {
     59 }
     60 
     61 void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
     62 {
     63     if (!item || !m_frame->view())
     64         return;
     65 
     66     item->setScrollPoint(m_frame->view()->scrollPosition());
     67 
     68     Page* page = m_frame->page();
     69     if (page && page->mainFrame() == m_frame)
     70         item->setPageScaleFactor(page->pageScaleFactor());
     71 }
     72 
     73 void HistoryController::clearScrollPositionAndViewState()
     74 {
     75     if (!m_currentItem)
     76         return;
     77 
     78     m_currentItem->clearScrollPoint();
     79     m_currentItem->setPageScaleFactor(0);
     80 }
     81 
     82 /*
     83  There is a race condition between the layout and load completion that affects restoring the scroll position.
     84  We try to restore the scroll position at both the first layout and upon load completion.
     85 
     86  1) If first layout happens before the load completes, we want to restore the scroll position then so that the
     87  first time we draw the page is already scrolled to the right place, instead of starting at the top and later
     88  jumping down.  It is possible that the old scroll position is past the part of the doc laid out so far, in
     89  which case the restore silent fails and we will fix it in when we try to restore on doc completion.
     90  2) If the layout happens after the load completes, the attempt to restore at load completion time silently
     91  fails.  We then successfully restore it when the layout happens.
     92 */
     93 void HistoryController::restoreScrollPositionAndViewState()
     94 {
     95     if (!m_frame->loader()->stateMachine()->committedFirstRealDocumentLoad())
     96         return;
     97 
     98     ASSERT(m_currentItem);
     99 
    100     // FIXME: As the ASSERT attests, it seems we should always have a currentItem here.
    101     // One counterexample is <rdar://problem/4917290>
    102     // For now, to cover this issue in release builds, there is no technical harm to returning
    103     // early and from a user standpoint - as in the above radar - the previous page load failed
    104     // so there *is* no scroll or view state to restore!
    105     if (!m_currentItem)
    106         return;
    107 
    108     if (FrameView* view = m_frame->view()) {
    109         Page* page = m_frame->page();
    110         if (page && page->mainFrame() == m_frame) {
    111             if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator())
    112                 scrollingCoordinator->frameViewRootLayerDidChange(view);
    113         }
    114 
    115         if (!view->wasScrolledByUser()) {
    116             if (page && page->mainFrame() == m_frame && m_currentItem->pageScaleFactor())
    117                 page->setPageScaleFactor(m_currentItem->pageScaleFactor(), m_currentItem->scrollPoint());
    118             else
    119                 view->setScrollPositionNonProgrammatically(m_currentItem->scrollPoint());
    120         }
    121     }
    122 }
    123 
    124 void HistoryController::updateBackForwardListForFragmentScroll()
    125 {
    126     updateBackForwardListClippedAtTarget(false);
    127 }
    128 
    129 void HistoryController::saveDocumentState()
    130 {
    131     if (!m_currentItem)
    132         return;
    133 
    134     Document* document = m_frame->document();
    135     ASSERT(document);
    136 
    137     if (m_currentItem->isCurrentDocument(document) && document->attached()) {
    138         LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame->tree()->uniqueName().string().utf8().data(), m_currentItem.get());
    139         m_currentItem->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 static inline bool isAssociatedToRequestedHistoryItem(const HistoryItem* current, Frame* frame, const HistoryItem* requested)
    154 {
    155     if (requested == current)
    156         return true;
    157     if (requested)
    158         return false;
    159     while ((frame = frame->tree()->parent())) {
    160         requested = frame->loader()->requestedHistoryItem();
    161         if (!requested)
    162             continue;
    163         if (requested->isAncestorOf(current))
    164             return true;
    165     }
    166     return false;
    167 }
    168 
    169 void HistoryController::restoreDocumentState()
    170 {
    171     Document* doc = m_frame->document();
    172 
    173     HistoryItem* itemToRestore = 0;
    174 
    175     switch (m_frame->loader()->loadType()) {
    176         case FrameLoadTypeReload:
    177         case FrameLoadTypeReloadFromOrigin:
    178         case FrameLoadTypeSame:
    179             break;
    180         case FrameLoadTypeBackForward:
    181         case FrameLoadTypeRedirectWithLockedBackForwardList:
    182         case FrameLoadTypeInitialInChildFrame:
    183         case FrameLoadTypeStandard:
    184             itemToRestore = m_currentItem.get();
    185     }
    186 
    187     if (!itemToRestore)
    188         return;
    189     if (isAssociatedToRequestedHistoryItem(itemToRestore, m_frame, m_frame->loader()->requestedHistoryItem()) && !m_frame->loader()->documentLoader()->isClientRedirect()) {
    190         LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame->tree()->uniqueName().string().utf8().data(), itemToRestore);
    191         doc->setStateForNewFormElements(itemToRestore->documentState());
    192     }
    193 }
    194 
    195 bool HistoryController::shouldStopLoadingForHistoryItem(HistoryItem* targetItem) const
    196 {
    197     if (!m_currentItem)
    198         return false;
    199 
    200     // Don't abort the current load if we're navigating within the current document.
    201     if (m_currentItem->shouldDoSameDocumentNavigationTo(targetItem))
    202         return false;
    203 
    204     return m_frame->loader()->client()->shouldStopLoadingForHistoryItem(targetItem);
    205 }
    206 
    207 // Main funnel for navigating to a previous location (back/forward, non-search snap-back)
    208 // This includes recursion to handle loading into framesets properly
    209 void HistoryController::goToItem(HistoryItem* targetItem)
    210 {
    211     ASSERT(!m_frame->tree()->parent());
    212 
    213     // shouldGoToHistoryItem is a private delegate method. This is needed to fix:
    214     // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls
    215     // Ultimately, history item navigations should go through the policy delegate. That's covered in:
    216     // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate
    217     Page* page = m_frame->page();
    218     if (!page)
    219         return;
    220     if (!m_frame->loader()->client()->shouldGoToHistoryItem(targetItem))
    221         return;
    222     if (m_defersLoading) {
    223         m_deferredItem = targetItem;
    224         return;
    225     }
    226 
    227     // Set the BF cursor before commit, which lets the user quickly click back/forward again.
    228     // - plus, it only makes sense for the top level of the operation through the frametree,
    229     // as opposed to happening for some/one of the page commits that might happen soon
    230     RefPtr<HistoryItem> currentItem = page->backForward()->currentItem();
    231     page->backForward()->setCurrentItem(targetItem);
    232 
    233     // First set the provisional item of any frames that are not actually navigating.
    234     // This must be done before trying to navigate the desired frame, because some
    235     // navigations can commit immediately (such as about:blank).  We must be sure that
    236     // all frames have provisional items set before the commit.
    237     recursiveSetProvisionalItem(targetItem, currentItem.get());
    238     // Now that all other frames have provisional items, do the actual navigation.
    239     recursiveGoToItem(targetItem, currentItem.get());
    240 }
    241 
    242 void HistoryController::setDefersLoading(bool defer)
    243 {
    244     m_defersLoading = defer;
    245     if (!defer && m_deferredItem) {
    246         goToItem(m_deferredItem.get());
    247         m_deferredItem = 0;
    248     }
    249 }
    250 
    251 void HistoryController::updateForBackForwardNavigation()
    252 {
    253 #if !LOG_DISABLED
    254     if (m_frame->loader()->documentLoader())
    255         LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
    256 #endif
    257 
    258     saveScrollPositionAndViewStateToItem(m_previousItem.get());
    259 
    260     // When traversing history, we may end up redirecting to a different URL
    261     // this time (e.g., due to cookies).  See http://webkit.org/b/49654.
    262     updateCurrentItem();
    263 }
    264 
    265 void HistoryController::updateForReload()
    266 {
    267 #if !LOG_DISABLED
    268     if (m_frame->loader()->documentLoader())
    269         LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
    270 #endif
    271 
    272     if (m_currentItem) {
    273         if (m_frame->loader()->loadType() == FrameLoadTypeReload || m_frame->loader()->loadType() == FrameLoadTypeReloadFromOrigin)
    274             saveScrollPositionAndViewStateToItem(m_currentItem.get());
    275     }
    276 
    277     // When reloading the page, we may end up redirecting to a different URL
    278     // this time (e.g., due to cookies).  See http://webkit.org/b/4072.
    279     updateCurrentItem();
    280 }
    281 
    282 // There are 2 things you might think of as "history", all of which are handled by these functions.
    283 //
    284 //     1) Back/forward: The m_currentItem is part of this mechanism.
    285 //     2) Global history: Handled by the client.
    286 //
    287 void HistoryController::updateForStandardLoad()
    288 {
    289     LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data());
    290 
    291     if (!m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
    292         updateBackForwardListClippedAtTarget(true);
    293 }
    294 
    295 void HistoryController::updateForRedirectWithLockedBackForwardList()
    296 {
    297 #if !LOG_DISABLED
    298     LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
    299 #endif
    300 
    301     if (!m_currentItem && !m_frame->tree()->parent()) {
    302         if (!m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
    303             updateBackForwardListClippedAtTarget(true);
    304     }
    305     // The client redirect replaces the current history item.
    306     updateCurrentItem();
    307 }
    308 
    309 void HistoryController::updateForInitialLoadInChildFrame()
    310 {
    311     Frame* parentFrame = m_frame->tree()->parent();
    312     if (parentFrame && parentFrame->loader()->history()->m_currentItem)
    313         parentFrame->loader()->history()->m_currentItem->setChildItem(createItem());
    314 }
    315 
    316 void HistoryController::updateForCommit()
    317 {
    318     FrameLoader* frameLoader = m_frame->loader();
    319 #if !LOG_DISABLED
    320     if (frameLoader->documentLoader())
    321         LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().string().utf8().data());
    322 #endif
    323     FrameLoadType type = frameLoader->loadType();
    324     if (isBackForwardLoadType(type) || (isReloadTypeWithProvisionalItem(type) && !frameLoader->documentLoader()->unreachableURL().isEmpty())) {
    325         // Once committed, we want to use current item for saving DocState, and
    326         // the provisional item for restoring state.
    327         // Note previousItem must be set before we close the URL, which will
    328         // happen when the data source is made non-provisional below
    329         m_previousItem = m_currentItem;
    330         ASSERT(m_provisionalItem);
    331         m_currentItem = m_provisionalItem;
    332         m_provisionalItem = 0;
    333 
    334         // Tell all other frames in the tree to commit their provisional items and
    335         // restore their scroll position.  We'll avoid this frame (which has already
    336         // committed) and its children (which will be replaced).
    337         Page* page = m_frame->page();
    338         ASSERT(page);
    339         page->mainFrame()->loader()->history()->recursiveUpdateForCommit();
    340     }
    341 
    342     switch (type) {
    343     case FrameLoadTypeBackForward:
    344         updateForBackForwardNavigation();
    345         return;
    346     case FrameLoadTypeReload:
    347     case FrameLoadTypeReloadFromOrigin:
    348     case FrameLoadTypeSame:
    349         updateForReload();
    350         return;
    351     case FrameLoadTypeStandard:
    352         updateForStandardLoad();
    353         return;
    354     case FrameLoadTypeRedirectWithLockedBackForwardList:
    355         updateForRedirectWithLockedBackForwardList();
    356         return;
    357     case FrameLoadTypeInitialInChildFrame:
    358         updateForInitialLoadInChildFrame();
    359         return;
    360     default:
    361         ASSERT_NOT_REACHED();
    362     }
    363 }
    364 
    365 bool HistoryController::isReloadTypeWithProvisionalItem(FrameLoadType type)
    366 {
    367     return (type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && m_provisionalItem;
    368 }
    369 
    370 void HistoryController::recursiveUpdateForCommit()
    371 {
    372     // The frame that navigated will now have a null provisional item.
    373     // Ignore it and its children.
    374     if (!m_provisionalItem)
    375         return;
    376 
    377     // For each frame that already had the content the item requested (based on
    378     // (a matching URL and frame tree snapshot), just restore the scroll position.
    379     // Save form state
    380     if (m_currentItem && itemsAreClones(m_currentItem.get(), m_provisionalItem.get())) {
    381         saveDocumentState();
    382         saveScrollPositionAndViewStateToItem(m_currentItem.get());
    383 
    384         if (FrameView* view = m_frame->view())
    385             view->setWasScrolledByUser(false);
    386 
    387         // Now commit the provisional item
    388         m_previousItem = m_currentItem;
    389         m_currentItem = m_provisionalItem;
    390         m_provisionalItem = 0;
    391 
    392         // Restore form state (works from currentItem)
    393         restoreDocumentState();
    394 
    395         // Restore the scroll position (we choose to do this rather than going back to the anchor point)
    396         restoreScrollPositionAndViewState();
    397     }
    398 
    399     // Iterate over the rest of the tree
    400     for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
    401         child->loader()->history()->recursiveUpdateForCommit();
    402 }
    403 
    404 void HistoryController::updateForSameDocumentNavigation()
    405 {
    406     if (m_frame->document()->url().isEmpty())
    407         return;
    408 
    409     Page* page = m_frame->page();
    410     if (!page)
    411         return;
    412 
    413     page->mainFrame()->loader()->history()->recursiveUpdateForSameDocumentNavigation();
    414 
    415     if (m_currentItem)
    416         m_currentItem->setURL(m_frame->document()->url());
    417 }
    418 
    419 void HistoryController::recursiveUpdateForSameDocumentNavigation()
    420 {
    421     // The frame that navigated will now have a null provisional item.
    422     // Ignore it and its children.
    423     if (!m_provisionalItem)
    424         return;
    425 
    426     // The provisional item may represent a different pending navigation.
    427     // Don't commit it if it isn't a same document navigation.
    428     if (m_currentItem && !m_currentItem->shouldDoSameDocumentNavigationTo(m_provisionalItem.get()))
    429         return;
    430 
    431     // Commit the provisional item.
    432     m_previousItem = m_currentItem;
    433     m_currentItem = m_provisionalItem;
    434     m_provisionalItem = 0;
    435 
    436     // Iterate over the rest of the tree.
    437     for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
    438         child->loader()->history()->recursiveUpdateForSameDocumentNavigation();
    439 }
    440 
    441 void HistoryController::setCurrentItem(HistoryItem* item)
    442 {
    443     m_previousItem = m_currentItem;
    444     m_currentItem = item;
    445 }
    446 
    447 void HistoryController::setCurrentItemTitle(const StringWithDirection& title)
    448 {
    449     if (m_currentItem)
    450         // FIXME: make use of title.direction() as well.
    451         m_currentItem->setTitle(title.string());
    452 }
    453 
    454 bool HistoryController::currentItemShouldBeReplaced() const
    455 {
    456     // From the HTML5 spec for location.assign():
    457     //  "If the browsing context's session history contains only one Document,
    458     //   and that was the about:blank Document created when the browsing context
    459     //   was created, then the navigation must be done with replacement enabled."
    460     return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
    461 }
    462 
    463 void HistoryController::setProvisionalItem(HistoryItem* item)
    464 {
    465     m_provisionalItem = item;
    466 }
    467 
    468 void HistoryController::initializeItem(HistoryItem* item)
    469 {
    470     DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
    471     ASSERT(documentLoader);
    472 
    473     KURL unreachableURL = documentLoader->unreachableURL();
    474 
    475     KURL url;
    476     KURL originalURL;
    477 
    478     if (!unreachableURL.isEmpty()) {
    479         url = unreachableURL;
    480         originalURL = unreachableURL;
    481     } else {
    482         url = documentLoader->url();
    483         originalURL = documentLoader->originalURL();
    484     }
    485 
    486     // Frames that have never successfully loaded any content
    487     // may have no URL at all. Currently our history code can't
    488     // deal with such things, so we nip that in the bud here.
    489     // Later we may want to learn to live with nil for URL.
    490     // See bug 3368236 and related bugs for more information.
    491     if (url.isEmpty())
    492         url = blankURL();
    493     if (originalURL.isEmpty())
    494         originalURL = blankURL();
    495 
    496     Frame* parentFrame = m_frame->tree()->parent();
    497     String parent = parentFrame ? parentFrame->tree()->uniqueName() : "";
    498     StringWithDirection title = documentLoader->title();
    499 
    500     item->setURL(url);
    501     item->setTarget(m_frame->tree()->uniqueName());
    502     item->setParent(parent);
    503     // FIXME: should store title directionality in history as well.
    504     item->setTitle(title.string());
    505     item->setOriginalURLString(originalURL.string());
    506 
    507     // Save form state if this is a POST
    508     item->setFormInfoFromRequest(documentLoader->request());
    509 }
    510 
    511 PassRefPtr<HistoryItem> HistoryController::createItem()
    512 {
    513     RefPtr<HistoryItem> item = HistoryItem::create();
    514     initializeItem(item.get());
    515 
    516     // Set the item for which we will save document state
    517     m_previousItem = m_currentItem;
    518     m_currentItem = item;
    519 
    520     return item.release();
    521 }
    522 
    523 PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
    524 {
    525     RefPtr<HistoryItem> bfItem = createItem();
    526     saveScrollPositionAndViewStateToItem(m_previousItem.get());
    527 
    528     if (!clipAtTarget || m_frame != targetFrame) {
    529         // save frame state for items that aren't loading (khtml doesn't save those)
    530         saveDocumentState();
    531 
    532         // clipAtTarget is false for navigations within the same document, so
    533         // we should copy the documentSequenceNumber over to the newly create
    534         // item.  Non-target items are just clones, and they should therefore
    535         // preserve the same itemSequenceNumber.
    536         if (m_previousItem) {
    537             if (m_frame != targetFrame)
    538                 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
    539             bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
    540         }
    541 
    542         for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
    543             // If the child is a frame corresponding to an <object> element that never loaded,
    544             // we don't want to create a history item, because that causes fallback content
    545             // to be ignored on reload.
    546             FrameLoader* childLoader = child->loader();
    547             if (childLoader->stateMachine()->startedFirstRealLoad() || !childLoader->isHostedByObjectElement())
    548                 bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget));
    549         }
    550     }
    551     // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
    552     if (m_frame == targetFrame)
    553         bfItem->setIsTargetItem(true);
    554     return bfItem;
    555 }
    556 
    557 // The general idea here is to traverse the frame tree and the item tree in parallel,
    558 // tracking whether each frame already has the content the item requests.  If there is
    559 // a match, we set the provisional item and recurse.  Otherwise we will reload that
    560 // frame and all its kids in recursiveGoToItem.
    561 void HistoryController::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem)
    562 {
    563     ASSERT(item);
    564 
    565     if (itemsAreClones(item, fromItem)) {
    566         // Set provisional item, which will be committed in recursiveUpdateForCommit.
    567         m_provisionalItem = item;
    568 
    569         const HistoryItemVector& childItems = item->children();
    570 
    571         int size = childItems.size();
    572 
    573         for (int i = 0; i < size; ++i) {
    574             String childFrameName = childItems[i]->target();
    575             HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
    576             ASSERT(fromChildItem);
    577             Frame* childFrame = m_frame->tree()->child(childFrameName);
    578             ASSERT(childFrame);
    579             childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem);
    580         }
    581     }
    582 }
    583 
    584 // We now traverse the frame tree and item tree a second time, loading frames that
    585 // do have the content the item requests.
    586 void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem)
    587 {
    588     ASSERT(item);
    589 
    590     if (itemsAreClones(item, fromItem)) {
    591         // Just iterate over the rest, looking for frames to navigate.
    592         const HistoryItemVector& childItems = item->children();
    593 
    594         int size = childItems.size();
    595         for (int i = 0; i < size; ++i) {
    596             String childFrameName = childItems[i]->target();
    597             HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
    598             ASSERT(fromChildItem);
    599             Frame* childFrame = m_frame->tree()->child(childFrameName);
    600             ASSERT(childFrame);
    601             childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem);
    602         }
    603     } else {
    604         m_frame->loader()->loadHistoryItem(item);
    605     }
    606 }
    607 
    608 bool HistoryController::itemsAreClones(HistoryItem* item1, HistoryItem* item2) const
    609 {
    610     // If the item we're going to is a clone of the item we're at, then we do
    611     // not need to load it again.  The current frame tree and the frame tree
    612     // snapshot in the item have to match.
    613     // Note: Some clients treat a navigation to the current history item as
    614     // a reload.  Thus, if item1 and item2 are the same, we need to create a
    615     // new document and should not consider them clones.
    616     // (See http://webkit.org/b/35532 for details.)
    617     return item1
    618         && item2
    619         && item1 != item2
    620         && item1->itemSequenceNumber() == item2->itemSequenceNumber()
    621         && currentFramesMatchItem(item1)
    622         && item2->hasSameFrames(item1);
    623 }
    624 
    625 // Helper method that determines whether the current frame tree matches given history item's.
    626 bool HistoryController::currentFramesMatchItem(HistoryItem* item) const
    627 {
    628     if ((!m_frame->tree()->uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame->tree()->uniqueName() != item->target())
    629         return false;
    630 
    631     const HistoryItemVector& childItems = item->children();
    632     if (childItems.size() != m_frame->tree()->childCount())
    633         return false;
    634 
    635     unsigned size = childItems.size();
    636     for (unsigned i = 0; i < size; ++i) {
    637         if (!m_frame->tree()->child(childItems[i]->target()))
    638             return false;
    639     }
    640 
    641     return true;
    642 }
    643 
    644 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
    645 {
    646     // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.
    647     // The item that was the target of the user's navigation is designated as the "targetItem".
    648     // When this function is called with doClip=true we're able to create the whole tree except for the target's children,
    649     // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
    650 
    651     Page* page = m_frame->page();
    652     if (!page)
    653         return;
    654 
    655     if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
    656         return;
    657 
    658     Frame* mainFrame = page->mainFrame();
    659     ASSERT(mainFrame);
    660 
    661     RefPtr<HistoryItem> topItem = mainFrame->loader()->history()->createItemTree(m_frame, doClip);
    662     LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
    663     page->backForward()->addItem(topItem.release());
    664 }
    665 
    666 void HistoryController::updateCurrentItem()
    667 {
    668     if (!m_currentItem)
    669         return;
    670 
    671     DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
    672 
    673     if (!documentLoader->unreachableURL().isEmpty())
    674         return;
    675 
    676     if (m_currentItem->url() != documentLoader->url()) {
    677         // We ended up on a completely different URL this time, so the HistoryItem
    678         // needs to be re-initialized.  Preserve the isTargetItem flag as it is a
    679         // property of how this HistoryItem was originally created and is not
    680         // dependent on the document.
    681         bool isTargetItem = m_currentItem->isTargetItem();
    682         m_currentItem->reset();
    683         initializeItem(m_currentItem.get());
    684         m_currentItem->setIsTargetItem(isTargetItem);
    685     } else {
    686         // Even if the final URL didn't change, the form data may have changed.
    687         m_currentItem->setFormInfoFromRequest(documentLoader->request());
    688     }
    689 }
    690 
    691 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
    692 {
    693     if (!m_currentItem)
    694         return;
    695 
    696     Page* page = m_frame->page();
    697     ASSERT(page);
    698 
    699     // Get a HistoryItem tree for the current frame tree.
    700     RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false);
    701 
    702     // Override data in the current item (created by createItemTree) to reflect
    703     // the pushState() arguments.
    704     m_currentItem->setTitle(title);
    705     m_currentItem->setStateObject(stateObject);
    706     m_currentItem->setURLString(urlString);
    707 
    708     page->backForward()->addItem(topItem.release());
    709 }
    710 
    711 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
    712 {
    713     if (!m_currentItem)
    714         return;
    715 
    716     if (!urlString.isEmpty())
    717         m_currentItem->setURLString(urlString);
    718     m_currentItem->setTitle(title);
    719     m_currentItem->setStateObject(stateObject);
    720     m_currentItem->setFormData(0);
    721     m_currentItem->setFormContentType(String());
    722 
    723     ASSERT(m_frame->page());
    724 }
    725 
    726 } // namespace WebCore
    727