<rdar://problem/9251515> and https://bugs.webkit.org/show_bug.cgi?id=58072
[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 "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 bool HistoryController::shouldStopLoadingForHistoryItem(HistoryItem* targetItem) const
221 {
222     if (!m_currentItem)
223         return false;
224
225     // Don't abort the current load if we're navigating within the current document.
226     if (m_currentItem->shouldDoSameDocumentNavigationTo(targetItem))
227         return false;
228
229     return m_frame->loader()->client()->shouldStopLoadingForHistoryItem(targetItem);
230 }
231
232 // Main funnel for navigating to a previous location (back/forward, non-search snap-back)
233 // This includes recursion to handle loading into framesets properly
234 void HistoryController::goToItem(HistoryItem* targetItem, FrameLoadType type)
235 {
236     ASSERT(!m_frame->tree()->parent());
237     
238     // shouldGoToHistoryItem is a private delegate method. This is needed to fix:
239     // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls
240     // Ultimately, history item navigations should go through the policy delegate. That's covered in:
241     // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate
242     Page* page = m_frame->page();
243     if (!page)
244         return;
245     if (!m_frame->loader()->client()->shouldGoToHistoryItem(targetItem))
246         return;
247
248     // Set the BF cursor before commit, which lets the user quickly click back/forward again.
249     // - plus, it only makes sense for the top level of the operation through the frametree,
250     // as opposed to happening for some/one of the page commits that might happen soon
251     RefPtr<HistoryItem> currentItem = page->backForward()->currentItem();
252     page->backForward()->setCurrentItem(targetItem);
253     m_frame->loader()->client()->updateGlobalHistoryItemForPage();
254
255     // First set the provisional item of any frames that are not actually navigating.
256     // This must be done before trying to navigate the desired frame, because some
257     // navigations can commit immediately (such as about:blank).  We must be sure that
258     // all frames have provisional items set before the commit.
259     recursiveSetProvisionalItem(targetItem, currentItem.get(), type);
260     // Now that all other frames have provisional items, do the actual navigation.
261     recursiveGoToItem(targetItem, currentItem.get(), type);
262 }
263
264 void HistoryController::updateForBackForwardNavigation()
265 {
266 #if !LOG_DISABLED
267     if (m_frame->loader()->documentLoader())
268         LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
269 #endif
270
271     // Must grab the current scroll position before disturbing it
272     if (!m_frameLoadComplete)
273         saveScrollPositionAndViewStateToItem(m_previousItem.get());
274
275     // When traversing history, we may end up redirecting to a different URL
276     // this time (e.g., due to cookies).  See http://webkit.org/b/49654.
277     updateCurrentItem();
278 }
279
280 void HistoryController::updateForReload()
281 {
282 #if !LOG_DISABLED
283     if (m_frame->loader()->documentLoader())
284         LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
285 #endif
286
287     if (m_currentItem) {
288         pageCache()->remove(m_currentItem.get());
289     
290         if (m_frame->loader()->loadType() == FrameLoadTypeReload || m_frame->loader()->loadType() == FrameLoadTypeReloadFromOrigin)
291             saveScrollPositionAndViewStateToItem(m_currentItem.get());
292     }
293
294     // When reloading the page, we may end up redirecting to a different URL
295     // this time (e.g., due to cookies).  See http://webkit.org/b/4072.
296     updateCurrentItem();
297 }
298
299 // There are 3 things you might think of as "history", all of which are handled by these functions.
300 //
301 //     1) Back/forward: The m_currentItem is part of this mechanism.
302 //     2) Global history: Handled by the client.
303 //     3) Visited links: Handled by the PageGroup.
304
305 void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
306 {
307     LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data());
308
309     FrameLoader* frameLoader = m_frame->loader();
310
311     Settings* settings = m_frame->settings();
312     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
313     const KURL& historyURL = frameLoader->documentLoader()->urlForHistory();
314
315     if (!frameLoader->documentLoader()->isClientRedirect()) {
316         if (!historyURL.isEmpty()) {
317             if (updateType != UpdateAllExceptBackForwardList)
318                 updateBackForwardListClippedAtTarget(true);
319             if (!needPrivacy) {
320                 frameLoader->client()->updateGlobalHistory();
321                 frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true);
322                 if (frameLoader->documentLoader()->unreachableURL().isEmpty())
323                     frameLoader->client()->updateGlobalHistoryRedirectLinks();
324             }
325
326             m_frame->loader()->client()->updateGlobalHistoryItemForPage();
327         }
328     } else {
329         // The client redirect replaces the current history item.
330         updateCurrentItem();
331     }
332
333     if (!historyURL.isEmpty() && !needPrivacy) {
334         if (Page* page = m_frame->page())
335             addVisitedLink(page, historyURL);
336
337         if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !m_frame->document()->url().isEmpty())
338             frameLoader->client()->updateGlobalHistoryRedirectLinks();
339     }
340 }
341
342 void HistoryController::updateForRedirectWithLockedBackForwardList()
343 {
344 #if !LOG_DISABLED
345     if (m_frame->loader()->documentLoader())
346         LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
347 #endif
348     
349     Settings* settings = m_frame->settings();
350     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
351     const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
352
353     if (m_frame->loader()->documentLoader()->isClientRedirect()) {
354         if (!m_currentItem && !m_frame->tree()->parent()) {
355             if (!historyURL.isEmpty()) {
356                 updateBackForwardListClippedAtTarget(true);
357                 if (!needPrivacy) {
358                     m_frame->loader()->client()->updateGlobalHistory();
359                     m_frame->loader()->documentLoader()->setDidCreateGlobalHistoryEntry(true);
360                     if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty())
361                         m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
362                 }
363
364                 m_frame->loader()->client()->updateGlobalHistoryItemForPage();
365             }
366         }
367         // The client redirect replaces the current history item.
368         updateCurrentItem();
369     } else {
370         Frame* parentFrame = m_frame->tree()->parent();
371         if (parentFrame && parentFrame->loader()->history()->m_currentItem)
372             parentFrame->loader()->history()->m_currentItem->setChildItem(createItem());
373     }
374
375     if (!historyURL.isEmpty() && !needPrivacy) {
376         if (Page* page = m_frame->page())
377             addVisitedLink(page, historyURL);
378
379         if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->document()->url().isEmpty())
380             m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
381     }
382 }
383
384 void HistoryController::updateForClientRedirect()
385 {
386 #if !LOG_DISABLED
387     if (m_frame->loader()->documentLoader())
388         LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
389 #endif
390
391     // Clear out form data so we don't try to restore it into the incoming page.  Must happen after
392     // webcore has closed the URL and saved away the form state.
393     if (m_currentItem) {
394         m_currentItem->clearDocumentState();
395         m_currentItem->clearScrollPoint();
396     }
397
398     Settings* settings = m_frame->settings();
399     bool needPrivacy = !settings || settings->privateBrowsingEnabled();
400     const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
401
402     if (!historyURL.isEmpty() && !needPrivacy) {
403         if (Page* page = m_frame->page())
404             addVisitedLink(page, historyURL);
405     }
406 }
407
408 void HistoryController::updateForCommit()
409 {
410     FrameLoader* frameLoader = m_frame->loader();
411 #if !LOG_DISABLED
412     if (frameLoader->documentLoader())
413         LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().string().utf8().data());
414 #endif
415     FrameLoadType type = frameLoader->loadType();
416     if (isBackForwardLoadType(type)
417         || isReplaceLoadTypeWithProvisionalItem(type)
418         || ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) {
419         // Once committed, we want to use current item for saving DocState, and
420         // the provisional item for restoring state.
421         // Note previousItem must be set before we close the URL, which will
422         // happen when the data source is made non-provisional below
423         m_frameLoadComplete = false;
424         m_previousItem = m_currentItem;
425         ASSERT(m_provisionalItem);
426         m_currentItem = m_provisionalItem;
427         m_provisionalItem = 0;
428
429         // Tell all other frames in the tree to commit their provisional items and
430         // restore their scroll position.  We'll avoid this frame (which has already
431         // committed) and its children (which will be replaced).
432         Page* page = m_frame->page();
433         ASSERT(page);
434         page->mainFrame()->loader()->history()->recursiveUpdateForCommit();
435     }
436 }
437
438 bool HistoryController::isReplaceLoadTypeWithProvisionalItem(FrameLoadType type)
439 {
440     // Going back to an error page in a subframe can trigger a FrameLoadTypeReplace
441     // while m_provisionalItem is set, so we need to commit it.
442     return type == FrameLoadTypeReplace && m_provisionalItem;
443 }
444
445 void HistoryController::recursiveUpdateForCommit()
446 {
447     // The frame that navigated will now have a null provisional item.
448     // Ignore it and its children.
449     if (!m_provisionalItem)
450         return;
451
452     // For each frame that already had the content the item requested (based on
453     // (a matching URL and frame tree snapshot), just restore the scroll position.
454     // Save form state (works from currentItem, since m_frameLoadComplete is true)
455     ASSERT(m_frameLoadComplete);
456     saveDocumentState();
457     saveScrollPositionAndViewStateToItem(m_currentItem.get());
458
459     if (FrameView* view = m_frame->view())
460         view->setWasScrolledByUser(false);
461
462     // Now commit the provisional item
463     m_frameLoadComplete = false;
464     m_previousItem = m_currentItem;
465     m_currentItem = m_provisionalItem;
466     m_provisionalItem = 0;
467
468     // Restore form state (works from currentItem)
469     restoreDocumentState();
470
471     // Restore the scroll position (we choose to do this rather than going back to the anchor point)
472     restoreScrollPositionAndViewState();
473
474     // Iterate over the rest of the tree
475     for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
476         child->loader()->history()->recursiveUpdateForCommit();
477 }
478
479 void HistoryController::updateForSameDocumentNavigation()
480 {
481     if (m_frame->document()->url().isEmpty())
482         return;
483
484     Settings* settings = m_frame->settings();
485     if (!settings || settings->privateBrowsingEnabled())
486         return;
487
488     Page* page = m_frame->page();
489     if (!page)
490         return;
491
492     addVisitedLink(page, m_frame->document()->url());
493     page->mainFrame()->loader()->history()->recursiveUpdateForSameDocumentNavigation();
494 }
495
496 void HistoryController::recursiveUpdateForSameDocumentNavigation()
497 {
498     // The frame that navigated will now have a null provisional item.
499     // Ignore it and its children.
500     if (!m_provisionalItem)
501         return;
502
503     // Commit the provisional item.
504     m_frameLoadComplete = false;
505     m_previousItem = m_currentItem;
506     m_currentItem = m_provisionalItem;
507     m_provisionalItem = 0;
508
509     // Iterate over the rest of the tree.
510     for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
511         child->loader()->history()->recursiveUpdateForSameDocumentNavigation();
512 }
513
514 void HistoryController::updateForFrameLoadCompleted()
515 {
516     // Even if already complete, we might have set a previous item on a frame that
517     // didn't do any data loading on the past transaction. Make sure to track that
518     // the load is complete so that we use the current item instead.
519     m_frameLoadComplete = true;
520 }
521
522 void HistoryController::setCurrentItem(HistoryItem* item)
523 {
524     m_frameLoadComplete = false;
525     m_previousItem = m_currentItem;
526     m_currentItem = item;
527 }
528
529 void HistoryController::setCurrentItemTitle(const StringWithDirection& title)
530 {
531     if (m_currentItem)
532         // FIXME: make use of title.direction() as well.
533         m_currentItem->setTitle(title.string());
534 }
535
536 bool HistoryController::currentItemShouldBeReplaced() const
537 {
538     // From the HTML5 spec for location.assign():
539     //  "If the browsing context's session history contains only one Document,
540     //   and that was the about:blank Document created when the browsing context
541     //   was created, then the navigation must be done with replacement enabled."
542     return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
543 }
544
545 void HistoryController::setProvisionalItem(HistoryItem* item)
546 {
547     m_provisionalItem = item;
548 }
549
550 void HistoryController::initializeItem(HistoryItem* item)
551 {
552     DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
553     ASSERT(documentLoader);
554
555     KURL unreachableURL = documentLoader->unreachableURL();
556
557     KURL url;
558     KURL originalURL;
559
560     if (!unreachableURL.isEmpty()) {
561         url = unreachableURL;
562         originalURL = unreachableURL;
563     } else {
564         url = documentLoader->url();
565         originalURL = documentLoader->originalURL();
566     }
567
568     // Frames that have never successfully loaded any content
569     // may have no URL at all. Currently our history code can't
570     // deal with such things, so we nip that in the bud here.
571     // Later we may want to learn to live with nil for URL.
572     // See bug 3368236 and related bugs for more information.
573     if (url.isEmpty()) 
574         url = blankURL();
575     if (originalURL.isEmpty())
576         originalURL = blankURL();
577     
578     Frame* parentFrame = m_frame->tree()->parent();
579     String parent = parentFrame ? parentFrame->tree()->uniqueName() : "";
580     StringWithDirection title = documentLoader->title();
581
582     item->setURL(url);
583     item->setTarget(m_frame->tree()->uniqueName());
584     item->setParent(parent);
585     // FIXME: should store title directionality in history as well.
586     item->setTitle(title.string());
587     item->setOriginalURLString(originalURL.string());
588
589     if (!unreachableURL.isEmpty() || documentLoader->response().httpStatusCode() >= 400)
590         item->setLastVisitWasFailure(true);
591
592     // Save form state if this is a POST
593     item->setFormInfoFromRequest(documentLoader->request());
594 }
595
596 PassRefPtr<HistoryItem> HistoryController::createItem()
597 {
598     RefPtr<HistoryItem> item = HistoryItem::create();
599     initializeItem(item.get());
600     
601     // Set the item for which we will save document state
602     m_frameLoadComplete = false;
603     m_previousItem = m_currentItem;
604     m_currentItem = item;
605     
606     return item.release();
607 }
608
609 PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
610 {
611     RefPtr<HistoryItem> bfItem = createItem();
612     if (!m_frameLoadComplete)
613         saveScrollPositionAndViewStateToItem(m_previousItem.get());
614
615     if (!clipAtTarget || m_frame != targetFrame) {
616         // save frame state for items that aren't loading (khtml doesn't save those)
617         saveDocumentState();
618
619         // clipAtTarget is false for navigations within the same document, so
620         // we should copy the documentSequenceNumber over to the newly create
621         // item.  Non-target items are just clones, and they should therefore
622         // preserve the same itemSequenceNumber.
623         if (m_previousItem) {
624             if (m_frame != targetFrame)
625                 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
626             bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
627         }
628
629         for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
630             FrameLoader* childLoader = child->loader();
631             bool hasChildLoaded = childLoader->frameHasLoaded();
632
633             // If the child is a frame corresponding to an <object> element that never loaded,
634             // we don't want to create a history item, because that causes fallback content
635             // to be ignored on reload.
636             
637             if (!(!hasChildLoaded && childLoader->isHostedByObjectElement()))
638                 bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget));
639         }
640     }
641     // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
642     if (m_frame == targetFrame)
643         bfItem->setIsTargetItem(true);
644     return bfItem;
645 }
646
647 // The general idea here is to traverse the frame tree and the item tree in parallel,
648 // tracking whether each frame already has the content the item requests.  If there is
649 // a match, we set the provisional item and recurse.  Otherwise we will reload that
650 // frame and all its kids in recursiveGoToItem.
651 void HistoryController::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
652 {
653     ASSERT(item);
654     ASSERT(fromItem);
655
656     if (itemsAreClones(item, fromItem)) {
657         // Set provisional item, which will be committed in recursiveUpdateForCommit.
658         m_provisionalItem = item;
659
660         const HistoryItemVector& childItems = item->children();
661
662         int size = childItems.size();
663
664         for (int i = 0; i < size; ++i) {
665             String childFrameName = childItems[i]->target();
666             HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
667             ASSERT(fromChildItem);
668             Frame* childFrame = m_frame->tree()->child(childFrameName);
669             ASSERT(childFrame);
670             childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem, type);
671         }
672     }
673 }
674
675 // We now traverse the frame tree and item tree a second time, loading frames that
676 // do have the content the item requests.
677 void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
678 {
679     ASSERT(item);
680     ASSERT(fromItem);
681
682     if (itemsAreClones(item, fromItem)) {
683         // Just iterate over the rest, looking for frames to navigate.
684         const HistoryItemVector& childItems = item->children();
685
686         int size = childItems.size();
687         for (int i = 0; i < size; ++i) {
688             String childFrameName = childItems[i]->target();
689             HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
690             ASSERT(fromChildItem);
691             Frame* childFrame = m_frame->tree()->child(childFrameName);
692             ASSERT(childFrame);
693             childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type);
694         }
695     } else {
696         m_frame->loader()->loadItem(item, type);
697     }
698 }
699
700 bool HistoryController::itemsAreClones(HistoryItem* item1, HistoryItem* item2) const
701 {
702     // If the item we're going to is a clone of the item we're at, then we do
703     // not need to load it again.  The current frame tree and the frame tree
704     // snapshot in the item have to match.
705     // Note: Some clients treat a navigation to the current history item as
706     // a reload.  Thus, if item1 and item2 are the same, we need to create a
707     // new document and should not consider them clones.
708     // (See http://webkit.org/b/35532 for details.)
709     return item1 != item2
710         && item1->itemSequenceNumber() == item2->itemSequenceNumber()
711         && currentFramesMatchItem(item1)
712         && item2->hasSameFrames(item1);
713 }
714
715 // Helper method that determines whether the current frame tree matches given history item's.
716 bool HistoryController::currentFramesMatchItem(HistoryItem* item) const
717 {
718     if ((!m_frame->tree()->uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame->tree()->uniqueName() != item->target())
719         return false;
720         
721     const HistoryItemVector& childItems = item->children();
722     if (childItems.size() != m_frame->tree()->childCount())
723         return false;
724     
725     unsigned size = childItems.size();
726     for (unsigned i = 0; i < size; ++i) {
727         if (!m_frame->tree()->child(childItems[i]->target()))
728             return false;
729     }
730     
731     return true;
732 }
733
734 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
735 {
736     // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.  
737     // The item that was the target of the user's navigation is designated as the "targetItem".  
738     // When this function is called with doClip=true we're able to create the whole tree except for the target's children, 
739     // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
740
741     Page* page = m_frame->page();
742     if (!page)
743         return;
744
745     if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
746         return;
747
748     Frame* mainFrame = page->mainFrame();
749     ASSERT(mainFrame);
750     FrameLoader* frameLoader = mainFrame->loader();
751
752     frameLoader->checkDidPerformFirstNavigation();
753
754     RefPtr<HistoryItem> topItem = frameLoader->history()->createItemTree(m_frame, doClip);
755     LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
756     page->backForward()->addItem(topItem.release());
757 }
758
759 void HistoryController::updateCurrentItem()
760 {
761     if (!m_currentItem)
762         return;
763
764     DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
765
766     if (!documentLoader->unreachableURL().isEmpty())
767         return;
768
769     if (m_currentItem->url() != documentLoader->url()) {
770         // We ended up on a completely different URL this time, so the HistoryItem
771         // needs to be re-initialized.  Preserve the isTargetItem flag as it is a
772         // property of how this HistoryItem was originally created and is not
773         // dependent on the document.
774         bool isTargetItem = m_currentItem->isTargetItem();
775         m_currentItem->reset();
776         initializeItem(m_currentItem.get());
777         m_currentItem->setIsTargetItem(isTargetItem);
778     } else {
779         // Even if the final URL didn't change, the form data may have changed.
780         m_currentItem->setFormInfoFromRequest(documentLoader->request());
781     }
782 }
783
784 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
785 {
786     if (!m_currentItem)
787         return;
788
789     Page* page = m_frame->page();
790     ASSERT(page);
791
792     // Get a HistoryItem tree for the current frame tree.
793     RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false);
794     
795     // Override data in the current item (created by createItemTree) to reflect
796     // the pushState() arguments.
797     m_currentItem->setTitle(title);
798     m_currentItem->setStateObject(stateObject);
799     m_currentItem->setURLString(urlString);
800
801     page->backForward()->addItem(topItem.release());
802
803     addVisitedLink(page, KURL(ParsedURLString, urlString));
804     m_frame->loader()->client()->updateGlobalHistory();
805
806 }
807
808 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
809 {
810     if (!m_currentItem)
811         return;
812
813     if (!urlString.isEmpty())
814         m_currentItem->setURLString(urlString);
815     m_currentItem->setTitle(title);
816     m_currentItem->setStateObject(stateObject);
817
818     ASSERT(m_frame->page());
819     addVisitedLink(m_frame->page(), KURL(ParsedURLString, urlString));
820     m_frame->loader()->client()->updateGlobalHistory();
821 }
822
823 } // namespace WebCore