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