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