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