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