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