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