2011-01-08 Charlie Reis <creis@chromium.org>
[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     // 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
239     // First set the provisional item of any frames that are not actually navigating.
240     // This must be done before trying to navigate the desired frame, because some
241     // navigations can commit immediately (such as about:blank).  We must be sure that
242     // all frames have provisional items set before the commit.
243     recursiveSetProvisionalItem(targetItem, currentItem, type);
244     // Now that all other frames have provisional items, do the actual navigation.
245     recursiveGoToItem(targetItem, currentItem, type);
246 }
247
248 void HistoryController::updateForBackForwardNavigation()
249 {
250 #if !LOG_DISABLED
251     if (m_frame->loader()->documentLoader())
252         LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
253 #endif
254
255     // Must grab the current scroll position before disturbing it
256     if (!m_frameLoadComplete)
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(HistoryUpdateType updateType)
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             if (updateType != UpdateAllExceptBackForwardList)
298                 updateBackForwardListClippedAtTarget(true);
299             if (!needPrivacy) {
300                 frameLoader->client()->updateGlobalHistory();
301                 frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true);
302                 if (frameLoader->documentLoader()->unreachableURL().isEmpty())
303                     frameLoader->client()->updateGlobalHistoryRedirectLinks();
304             }
305             if (Page* page = m_frame->page())
306                 page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForward()->currentItem());
307         }
308     } else if (frameLoader->documentLoader()->unreachableURL().isEmpty() && m_currentItem) {
309         m_currentItem->setURL(frameLoader->documentLoader()->url());
310         m_currentItem->setFormInfoFromRequest(frameLoader->documentLoader()->request());
311     }
312
313     if (!historyURL.isEmpty() && !needPrivacy) {
314         if (Page* page = m_frame->page())
315             addVisitedLink(page, historyURL);
316
317         if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !frameLoader->url().isEmpty())
318             frameLoader->client()->updateGlobalHistoryRedirectLinks();
319     }
320 }
321
322 void HistoryController::updateForRedirectWithLockedBackForwardList()
323 {
324 #if !LOG_DISABLED
325     if (m_frame->loader()->documentLoader())
326         LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
327 #endif
328     
329     Settings* settings = m_frame->settings();
330     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
331     const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
332
333     if (m_frame->loader()->documentLoader()->isClientRedirect()) {
334         if (!m_currentItem && !m_frame->tree()->parent()) {
335             if (!historyURL.isEmpty()) {
336                 updateBackForwardListClippedAtTarget(true);
337                 if (!needPrivacy) {
338                     m_frame->loader()->client()->updateGlobalHistory();
339                     m_frame->loader()->documentLoader()->setDidCreateGlobalHistoryEntry(true);
340                     if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty())
341                         m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
342                 }
343                 if (Page* page = m_frame->page())
344                     page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForward()->currentItem());
345             }
346         }
347         if (m_currentItem) {
348             m_currentItem->setURL(m_frame->loader()->documentLoader()->url());
349             m_currentItem->setFormInfoFromRequest(m_frame->loader()->documentLoader()->request());
350         }
351     } else {
352         Frame* parentFrame = m_frame->tree()->parent();
353         if (parentFrame && parentFrame->loader()->history()->m_currentItem)
354             parentFrame->loader()->history()->m_currentItem->setChildItem(createItem(true));
355     }
356
357     if (!historyURL.isEmpty() && !needPrivacy) {
358         if (Page* page = m_frame->page())
359             addVisitedLink(page, historyURL);
360
361         if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->loader()->url().isEmpty())
362             m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
363     }
364 }
365
366 void HistoryController::updateForClientRedirect()
367 {
368 #if !LOG_DISABLED
369     if (m_frame->loader()->documentLoader())
370         LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
371 #endif
372
373     // Clear out form data so we don't try to restore it into the incoming page.  Must happen after
374     // webcore has closed the URL and saved away the form state.
375     if (m_currentItem) {
376         m_currentItem->clearDocumentState();
377         m_currentItem->clearScrollPoint();
378     }
379
380     Settings* settings = m_frame->settings();
381     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
382     const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
383
384     if (!historyURL.isEmpty() && !needPrivacy) {
385         if (Page* page = m_frame->page())
386             addVisitedLink(page, historyURL);
387     }
388 }
389
390 void HistoryController::updateForCommit()
391 {
392     FrameLoader* frameLoader = m_frame->loader();
393 #if !LOG_DISABLED
394     if (frameLoader->documentLoader())
395         LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().utf8().data());
396 #endif
397     FrameLoadType type = frameLoader->loadType();
398     if (isBackForwardLoadType(type) ||
399         ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) {
400         // Once committed, we want to use current item for saving DocState, and
401         // the provisional item for restoring state.
402         // Note previousItem must be set before we close the URL, which will
403         // happen when the data source is made non-provisional below
404         m_frameLoadComplete = false;
405         m_previousItem = m_currentItem;
406         ASSERT(m_provisionalItem);
407         m_currentItem = m_provisionalItem;
408         m_provisionalItem = 0;
409
410         // Tell all other frames in the tree to commit their provisional items and
411         // restore their scroll position.  We'll avoid this frame (which has already
412         // committed) and its children (which will be replaced).
413         Page* page = m_frame->page();
414         ASSERT(page);
415         page->mainFrame()->loader()->history()->recursiveUpdateForCommit();
416     }
417 }
418
419 void HistoryController::recursiveUpdateForCommit()
420 {
421     // The frame that navigated will now have a null provisional item.
422     // Ignore it and its children.
423     if (!m_provisionalItem)
424         return;
425
426     // For each frame that already had the content the item requested (based on
427     // (a matching URL and frame tree snapshot), just restore the scroll position.
428     // Save form state (works from currentItem, since m_frameLoadComplete is true)
429     ASSERT(m_frameLoadComplete);
430     saveDocumentState();
431     saveScrollPositionAndViewStateToItem(m_currentItem.get());
432
433     if (FrameView* view = m_frame->view())
434         view->setWasScrolledByUser(false);
435
436     // Now commit the provisional item
437     m_frameLoadComplete = false;
438     m_previousItem = m_currentItem;
439     m_currentItem = m_provisionalItem;
440     m_provisionalItem = 0;
441
442     // Restore form state (works from currentItem)
443     restoreDocumentState();
444
445     // Restore the scroll position (we choose to do this rather than going back to the anchor point)
446     restoreScrollPositionAndViewState();
447
448     // Iterate over the rest of the tree
449     for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
450         child->loader()->history()->recursiveUpdateForCommit();
451 }
452
453 void HistoryController::updateForSameDocumentNavigation()
454 {
455     if (m_frame->loader()->url().isEmpty())
456         return;
457
458     Settings* settings = m_frame->settings();
459     if (!settings || settings->privateBrowsingEnabled())
460         return;
461
462     Page* page = m_frame->page();
463     if (!page)
464         return;
465
466     addVisitedLink(page, m_frame->loader()->url());
467 }
468
469 void HistoryController::updateForFrameLoadCompleted()
470 {
471     // Even if already complete, we might have set a previous item on a frame that
472     // didn't do any data loading on the past transaction. Make sure to track that
473     // the load is complete so that we use the current item instead.
474     m_frameLoadComplete = true;
475 }
476
477 void HistoryController::setCurrentItem(HistoryItem* item)
478 {
479     m_frameLoadComplete = false;
480     m_previousItem = m_currentItem;
481     m_currentItem = item;
482 }
483
484 void HistoryController::setCurrentItemTitle(const String& title)
485 {
486     if (m_currentItem)
487         m_currentItem->setTitle(title);
488 }
489
490 bool HistoryController::currentItemShouldBeReplaced() const
491 {
492     // From the HTML5 spec for location.assign():
493     //  "If the browsing context's session history contains only one Document,
494     //   and that was the about:blank Document created when the browsing context
495     //   was created, then the navigation must be done with replacement enabled."
496     return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
497 }
498
499 void HistoryController::setProvisionalItem(HistoryItem* item)
500 {
501     m_provisionalItem = item;
502 }
503
504 PassRefPtr<HistoryItem> HistoryController::createItem(bool useOriginal)
505 {
506     DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
507     
508     KURL unreachableURL = documentLoader ? documentLoader->unreachableURL() : KURL();
509     
510     KURL url;
511     KURL originalURL;
512
513     if (!unreachableURL.isEmpty()) {
514         url = unreachableURL;
515         originalURL = unreachableURL;
516     } else {
517         originalURL = documentLoader ? documentLoader->originalURL() : KURL();
518         if (useOriginal)
519             url = originalURL;
520         else if (documentLoader)
521             url = documentLoader->requestURL();
522     }
523
524     LOG(History, "WebCoreHistory: Creating item for %s", url.string().ascii().data());
525     
526     // Frames that have never successfully loaded any content
527     // may have no URL at all. Currently our history code can't
528     // deal with such things, so we nip that in the bud here.
529     // Later we may want to learn to live with nil for URL.
530     // See bug 3368236 and related bugs for more information.
531     if (url.isEmpty()) 
532         url = blankURL();
533     if (originalURL.isEmpty())
534         originalURL = blankURL();
535     
536     Frame* parentFrame = m_frame->tree()->parent();
537     String parent = parentFrame ? parentFrame->tree()->uniqueName() : "";
538     String title = documentLoader ? documentLoader->title() : "";
539
540     RefPtr<HistoryItem> item = HistoryItem::create(url, m_frame->tree()->uniqueName(), parent, title);
541     item->setOriginalURLString(originalURL.string());
542
543     if (!unreachableURL.isEmpty() || !documentLoader || documentLoader->response().httpStatusCode() >= 400)
544         item->setLastVisitWasFailure(true);
545
546     // Save form state if this is a POST
547     if (documentLoader) {
548         if (useOriginal)
549             item->setFormInfoFromRequest(documentLoader->originalRequest());
550         else
551             item->setFormInfoFromRequest(documentLoader->request());
552     }
553     
554     // Set the item for which we will save document state
555     m_frameLoadComplete = false;
556     m_previousItem = m_currentItem;
557     m_currentItem = item;
558     
559     return item.release();
560 }
561
562 PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
563 {
564     RefPtr<HistoryItem> bfItem = createItem(m_frame->tree()->parent() ? true : false);
565     if (!m_frameLoadComplete)
566         saveScrollPositionAndViewStateToItem(m_previousItem.get());
567
568     if (!clipAtTarget || m_frame != targetFrame) {
569         // save frame state for items that aren't loading (khtml doesn't save those)
570         saveDocumentState();
571
572         // clipAtTarget is false for navigations within the same document, so
573         // we should copy the documentSequenceNumber over to the newly create
574         // item.  Non-target items are just clones, and they should therefore
575         // preserve the same itemSequenceNumber.
576         if (m_previousItem) {
577             if (m_frame != targetFrame)
578                 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
579             bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
580         }
581
582         for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
583             FrameLoader* childLoader = child->loader();
584             bool hasChildLoaded = childLoader->frameHasLoaded();
585
586             // If the child is a frame corresponding to an <object> element that never loaded,
587             // we don't want to create a history item, because that causes fallback content
588             // to be ignored on reload.
589             
590             if (!(!hasChildLoaded && childLoader->isHostedByObjectElement()))
591                 bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget));
592         }
593     }
594     // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
595     if (m_frame == targetFrame)
596         bfItem->setIsTargetItem(true);
597     return bfItem;
598 }
599
600 // The general idea here is to traverse the frame tree and the item tree in parallel,
601 // tracking whether each frame already has the content the item requests.  If there is
602 // a match, we set the provisional item and recurse.  Otherwise we will reload that
603 // frame and all its kids in recursiveGoToItem.
604 void HistoryController::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
605 {
606     ASSERT(item);
607     ASSERT(fromItem);
608
609     if (itemsAreClones(item, fromItem)) {
610         // Set provisional item, which will be committed in recursiveUpdateForCommit.
611         m_provisionalItem = item;
612
613         const HistoryItemVector& childItems = item->children();
614
615         int size = childItems.size();
616         for (int i = 0; i < size; ++i) {
617             String childFrameName = childItems[i]->target();
618             HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
619             ASSERT(fromChildItem);
620             Frame* childFrame = m_frame->tree()->child(childFrameName);
621             ASSERT(childFrame);
622             childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem, type);
623         }
624     }
625 }
626
627 // We now traverse the frame tree and item tree a second time, loading frames that
628 // do have the content the item requests.
629 void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
630 {
631     ASSERT(item);
632     ASSERT(fromItem);
633
634     if (itemsAreClones(item, fromItem)) {
635         // Just iterate over the rest, looking for frames to navigate.
636         const HistoryItemVector& childItems = item->children();
637
638         int size = childItems.size();
639         for (int i = 0; i < size; ++i) {
640             String childFrameName = childItems[i]->target();
641             HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
642             ASSERT(fromChildItem);
643             Frame* childFrame = m_frame->tree()->child(childFrameName);
644             ASSERT(childFrame);
645             childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type);
646         }
647     } else {
648         m_frame->loader()->loadItem(item, type);
649     }
650 }
651
652 bool HistoryController::itemsAreClones(HistoryItem* item1, HistoryItem* item2) const
653 {
654     // If the item we're going to is a clone of the item we're at, then we do
655     // not need to load it again.  The current frame tree and the frame tree
656     // snapshot in the item have to match.
657     // Note: Some clients treat a navigation to the current history item as
658     // a reload.  Thus, if item1 and item2 are the same, we need to create a
659     // new document and should not consider them clones.
660     // (See http://webkit.org/b/35532 for details.)
661     return item1 != item2
662         && item1->itemSequenceNumber() == item2->itemSequenceNumber()
663         && currentFramesMatchItem(item1)
664         && item2->hasSameFrames(item1);
665 }
666
667 // Helper method that determines whether the current frame tree matches given history item's.
668 bool HistoryController::currentFramesMatchItem(HistoryItem* item) const
669 {
670     if ((!m_frame->tree()->uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame->tree()->uniqueName() != item->target())
671         return false;
672         
673     const HistoryItemVector& childItems = item->children();
674     if (childItems.size() != m_frame->tree()->childCount())
675         return false;
676     
677     unsigned size = childItems.size();
678     for (unsigned i = 0; i < size; ++i) {
679         if (!m_frame->tree()->child(childItems[i]->target()))
680             return false;
681     }
682     
683     return true;
684 }
685
686 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
687 {
688     // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.  
689     // The item that was the target of the user's navigation is designated as the "targetItem".  
690     // When this function is called with doClip=true we're able to create the whole tree except for the target's children, 
691     // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
692
693     Page* page = m_frame->page();
694     if (!page)
695         return;
696
697     if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
698         return;
699
700     Frame* mainFrame = page->mainFrame();
701     ASSERT(mainFrame);
702     FrameLoader* frameLoader = mainFrame->loader();
703
704     frameLoader->checkDidPerformFirstNavigation();
705
706     RefPtr<HistoryItem> topItem = frameLoader->history()->createItemTree(m_frame, doClip);
707     LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
708     page->backForward()->addItem(topItem.release());
709 }
710
711 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
712 {
713     if (!m_currentItem)
714         return;
715
716     Page* page = m_frame->page();
717     ASSERT(page);
718
719     // Get a HistoryItem tree for the current frame tree.
720     RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false);
721     
722     // Override data in the current item (created by createItemTree) to reflect
723     // the pushState() arguments.
724     m_currentItem->setTitle(title);
725     m_currentItem->setStateObject(stateObject);
726     m_currentItem->setURLString(urlString);
727
728     page->backForward()->addItem(topItem.release());
729 }
730
731 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
732 {
733     if (!m_currentItem)
734         return;
735
736     if (!urlString.isEmpty())
737         m_currentItem->setURLString(urlString);
738     m_currentItem->setTitle(title);
739     m_currentItem->setStateObject(stateObject);
740 }
741
742 } // namespace WebCore