Add a DefaultVisitedLinkProvider and route visited link actions through it
[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 "ScrollingCoordinator.h"
50 #include "Settings.h"
51 #include "VisitedLinkProvider.h"
52 #include <wtf/text/CString.h>
53
54 namespace WebCore {
55
56 static inline void addVisitedLink(Page& page, const URL& url)
57 {
58     page.visitedLinkProvider().addVisitedLink(page, visitedLinkHash(url.string()));
59 }
60
61 HistoryController::HistoryController(Frame& frame)
62     : m_frame(frame)
63     , m_frameLoadComplete(true)
64     , m_defersLoading(false)
65 {
66 }
67
68 HistoryController::~HistoryController()
69 {
70 }
71
72 void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
73 {
74     if (!item || !m_frame.view())
75         return;
76
77     if (m_frame.document()->inPageCache())
78         item->setScrollPoint(m_frame.view()->cachedScrollPosition());
79     else
80         item->setScrollPoint(m_frame.view()->scrollPosition());
81
82     Page* page = m_frame.page();
83     if (page && m_frame.isMainFrame())
84         item->setPageScaleFactor(page->pageScaleFactor());
85
86     // FIXME: It would be great to work out a way to put this code in WebCore instead of calling through to the client.
87     m_frame.loader().client().saveViewStateToItem(item);
88 }
89
90 void HistoryController::clearScrollPositionAndViewState()
91 {
92     if (!m_currentItem)
93         return;
94
95     m_currentItem->clearScrollPoint();
96     m_currentItem->setPageScaleFactor(0);
97 }
98
99 /*
100  There is a race condition between the layout and load completion that affects restoring the scroll position.
101  We try to restore the scroll position at both the first layout and upon load completion.
102  
103  1) If first layout happens before the load completes, we want to restore the scroll position then so that the
104  first time we draw the page is already scrolled to the right place, instead of starting at the top and later
105  jumping down.  It is possible that the old scroll position is past the part of the doc laid out so far, in
106  which case the restore silent fails and we will fix it in when we try to restore on doc completion.
107  2) If the layout happens after the load completes, the attempt to restore at load completion time silently
108  fails.  We then successfully restore it when the layout happens.
109 */
110 void HistoryController::restoreScrollPositionAndViewState()
111 {
112     if (!m_frame.loader().stateMachine().committedFirstRealDocumentLoad())
113         return;
114
115     ASSERT(m_currentItem);
116     
117     // FIXME: As the ASSERT attests, it seems we should always have a currentItem here.
118     // One counterexample is <rdar://problem/4917290>
119     // For now, to cover this issue in release builds, there is no technical harm to returning
120     // early and from a user standpoint - as in the above radar - the previous page load failed 
121     // so there *is* no scroll or view state to restore!
122     if (!m_currentItem)
123         return;
124     
125     // FIXME: It would be great to work out a way to put this code in WebCore instead of calling
126     // through to the client. It's currently used only for the PDF view on Mac.
127     m_frame.loader().client().restoreViewState();
128
129     // Don't restore scroll point on iOS as FrameLoaderClient::restoreViewState() does that.
130 #if !PLATFORM(IOS)
131     // FIXME: There is some scrolling related work that needs to happen whenever a page goes into the
132     // page cache and similar work that needs to occur when it comes out. This is where we do the work
133     // that needs to happen when we exit, and the work that needs to happen when we enter is in
134     // Document::setIsInPageCache(bool). It would be nice if there was more symmetry in these spots.
135     // https://bugs.webkit.org/show_bug.cgi?id=98698
136     if (FrameView* view = m_frame.view()) {
137         Page* page = m_frame.page();
138         if (page && m_frame.isMainFrame()) {
139             if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator())
140                 scrollingCoordinator->frameViewRootLayerDidChange(view);
141         }
142
143         if (!view->wasScrolledByUser()) {
144             if (page && m_frame.isMainFrame() && m_currentItem->pageScaleFactor())
145                 page->setPageScaleFactor(m_currentItem->pageScaleFactor(), m_currentItem->scrollPoint());
146             else
147                 view->setScrollPosition(m_currentItem->scrollPoint());
148         }
149     }
150 #endif
151 }
152
153 void HistoryController::updateBackForwardListForFragmentScroll()
154 {
155     updateBackForwardListClippedAtTarget(false);
156 }
157
158 void HistoryController::saveDocumentState()
159 {
160     // FIXME: Reading this bit of FrameLoader state here is unfortunate.  I need to study
161     // this more to see if we can remove this dependency.
162     if (m_frame.loader().stateMachine().creatingInitialEmptyDocument())
163         return;
164
165     // For a standard page load, we will have a previous item set, which will be used to
166     // store the form state.  However, in some cases we will have no previous item, and
167     // the current item is the right place to save the state.  One example is when we
168     // detach a bunch of frames because we are navigating from a site with frames to
169     // another site.  Another is when saving the frame state of a frame that is not the
170     // target of the current navigation (if we even decide to save with that granularity).
171
172     // Because of previousItem's "masking" of currentItem for this purpose, it's important
173     // that we keep track of the end of a page transition with m_frameLoadComplete.  We
174     // leverage the checkLoadComplete recursion to achieve this goal.
175
176     HistoryItem* item = m_frameLoadComplete ? m_currentItem.get() : m_previousItem.get();
177     if (!item)
178         return;
179
180     Document* document = m_frame.document();
181     ASSERT(document);
182     
183     if (item->isCurrentDocument(document) && document->hasLivingRenderTree()) {
184         LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame.tree().uniqueName().string().utf8().data(), item);
185         item->setDocumentState(document->formElementsState());
186     }
187 }
188
189 // Walk the frame tree, telling all frames to save their form state into their current
190 // history item.
191 void HistoryController::saveDocumentAndScrollState()
192 {
193     for (Frame* frame = &m_frame; frame; frame = frame->tree().traverseNext(&m_frame)) {
194         frame->loader().history().saveDocumentState();
195         frame->loader().history().saveScrollPositionAndViewStateToItem(frame->loader().history().currentItem());
196     }
197 }
198
199 void HistoryController::restoreDocumentState()
200 {
201     switch (m_frame.loader().loadType()) {
202         case FrameLoadTypeReload:
203         case FrameLoadTypeReloadFromOrigin:
204         case FrameLoadTypeSame:
205         case FrameLoadTypeReplace:
206             // Not restoring the document state.
207             return;
208         case FrameLoadTypeBack:
209         case FrameLoadTypeForward:
210         case FrameLoadTypeIndexedBackForward:
211         case FrameLoadTypeRedirectWithLockedBackForwardList:
212         case FrameLoadTypeStandard:
213             break;
214     }
215     
216     if (!m_currentItem)
217         return;
218     if (m_frame.loader().requestedHistoryItem() != m_currentItem.get())
219         return;
220     if (m_frame.loader().documentLoader()->isClientRedirect())
221         return;
222
223     LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame.tree().uniqueName().string().utf8().data(), m_currentItem.get());
224     m_frame.document()->setStateForNewFormElements(m_currentItem->documentState());
225 }
226
227 void HistoryController::invalidateCurrentItemCachedPage()
228 {
229     // When we are pre-commit, the currentItem is where any page cache data resides.
230     if (!pageCache()->get(currentItem()))
231         return;
232
233     std::unique_ptr<CachedPage> cachedPage = pageCache()->take(currentItem());
234
235     // FIXME: This is a grotesque hack to fix <rdar://problem/4059059> Crash in RenderFlow::detach
236     // Somehow the PageState object is not properly updated, and is holding onto a stale document.
237     // Both Xcode and FileMaker see this crash, Safari does not.
238     
239     ASSERT(cachedPage->document() == m_frame.document());
240     if (cachedPage->document() == m_frame.document()) {
241         cachedPage->document()->setInPageCache(false);
242         cachedPage->clear();
243     }
244 }
245
246 bool HistoryController::shouldStopLoadingForHistoryItem(HistoryItem* targetItem) const
247 {
248     if (!m_currentItem)
249         return false;
250
251     // Don't abort the current load if we're navigating within the current document.
252     if (m_currentItem->shouldDoSameDocumentNavigationTo(targetItem))
253         return false;
254
255     return true;
256 }
257
258 // Main funnel for navigating to a previous location (back/forward, non-search snap-back)
259 // This includes recursion to handle loading into framesets properly
260 void HistoryController::goToItem(HistoryItem* targetItem, FrameLoadType type)
261 {
262     ASSERT(!m_frame.tree().parent());
263     
264     // shouldGoToHistoryItem is a private delegate method. This is needed to fix:
265     // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls
266     // Ultimately, history item navigations should go through the policy delegate. That's covered in:
267     // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate
268     Page* page = m_frame.page();
269     if (!page)
270         return;
271     if (!m_frame.loader().client().shouldGoToHistoryItem(targetItem))
272         return;
273     if (m_defersLoading) {
274         m_deferredItem = targetItem;
275         m_deferredFrameLoadType = type;
276         return;
277     }
278
279     // Set the BF cursor before commit, which lets the user quickly click back/forward again.
280     // - plus, it only makes sense for the top level of the operation through the frametree,
281     // as opposed to happening for some/one of the page commits that might happen soon
282     RefPtr<HistoryItem> currentItem = page->backForward().currentItem();
283     page->backForward().setCurrentItem(targetItem);
284     m_frame.loader().client().updateGlobalHistoryItemForPage();
285
286     // First set the provisional item of any frames that are not actually navigating.
287     // This must be done before trying to navigate the desired frame, because some
288     // navigations can commit immediately (such as about:blank).  We must be sure that
289     // all frames have provisional items set before the commit.
290     recursiveSetProvisionalItem(targetItem, currentItem.get(), type);
291     // Now that all other frames have provisional items, do the actual navigation.
292     recursiveGoToItem(targetItem, currentItem.get(), type);
293 }
294
295 void HistoryController::setDefersLoading(bool defer)
296 {
297     m_defersLoading = defer;
298     if (!defer && m_deferredItem) {
299         goToItem(m_deferredItem.get(), m_deferredFrameLoadType);
300         m_deferredItem = 0;
301     }
302 }
303
304 void HistoryController::updateForBackForwardNavigation()
305 {
306 #if !LOG_DISABLED
307     if (m_frame.loader().documentLoader())
308         LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame.loader().documentLoader()->title().string().utf8().data());
309 #endif
310
311     // Must grab the current scroll position before disturbing it
312     if (!m_frameLoadComplete)
313         saveScrollPositionAndViewStateToItem(m_previousItem.get());
314
315     // When traversing history, we may end up redirecting to a different URL
316     // this time (e.g., due to cookies).  See http://webkit.org/b/49654.
317     updateCurrentItem();
318 }
319
320 void HistoryController::updateForReload()
321 {
322 #if !LOG_DISABLED
323     if (m_frame.loader().documentLoader())
324         LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame.loader().documentLoader()->title().string().utf8().data());
325 #endif
326
327     if (m_currentItem) {
328         pageCache()->remove(m_currentItem.get());
329     
330         if (m_frame.loader().loadType() == FrameLoadTypeReload || m_frame.loader().loadType() == FrameLoadTypeReloadFromOrigin)
331             saveScrollPositionAndViewStateToItem(m_currentItem.get());
332     }
333
334     // When reloading the page, we may end up redirecting to a different URL
335     // this time (e.g., due to cookies).  See http://webkit.org/b/4072.
336     updateCurrentItem();
337 }
338
339 // There are 3 things you might think of as "history", all of which are handled by these functions.
340 //
341 //     1) Back/forward: The m_currentItem is part of this mechanism.
342 //     2) Global history: Handled by the client.
343 //     3) Visited links: Handled by the PageGroup.
344
345 void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
346 {
347     LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame.loader().documentLoader()->url().string().ascii().data());
348
349     FrameLoader& frameLoader = m_frame.loader();
350
351     bool needPrivacy = m_frame.settings().privateBrowsingEnabled();
352     const URL& historyURL = frameLoader.documentLoader()->urlForHistory();
353
354     if (!frameLoader.documentLoader()->isClientRedirect()) {
355         if (!historyURL.isEmpty()) {
356             if (updateType != UpdateAllExceptBackForwardList)
357                 updateBackForwardListClippedAtTarget(true);
358             if (!needPrivacy) {
359                 frameLoader.client().updateGlobalHistory();
360                 frameLoader.documentLoader()->setDidCreateGlobalHistoryEntry(true);
361                 if (frameLoader.documentLoader()->unreachableURL().isEmpty())
362                     frameLoader.client().updateGlobalHistoryRedirectLinks();
363             }
364
365             m_frame.loader().client().updateGlobalHistoryItemForPage();
366         }
367     } else {
368         // The client redirect replaces the current history item.
369         updateCurrentItem();
370     }
371
372     if (!historyURL.isEmpty() && !needPrivacy) {
373         if (Page* page = m_frame.page())
374             addVisitedLink(*page, historyURL);
375
376         if (!frameLoader.documentLoader()->didCreateGlobalHistoryEntry() && frameLoader.documentLoader()->unreachableURL().isEmpty() && !m_frame.document()->url().isEmpty())
377             frameLoader.client().updateGlobalHistoryRedirectLinks();
378     }
379 }
380
381 void HistoryController::updateForRedirectWithLockedBackForwardList()
382 {
383 #if !LOG_DISABLED
384     if (m_frame.loader().documentLoader())
385         LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame.loader().documentLoader()->title().string().utf8().data());
386 #endif
387     
388     bool needPrivacy = m_frame.settings().privateBrowsingEnabled();
389     const URL& historyURL = m_frame.loader().documentLoader()->urlForHistory();
390
391     if (m_frame.loader().documentLoader()->isClientRedirect()) {
392         if (!m_currentItem && !m_frame.tree().parent()) {
393             if (!historyURL.isEmpty()) {
394                 updateBackForwardListClippedAtTarget(true);
395                 if (!needPrivacy) {
396                     m_frame.loader().client().updateGlobalHistory();
397                     m_frame.loader().documentLoader()->setDidCreateGlobalHistoryEntry(true);
398                     if (m_frame.loader().documentLoader()->unreachableURL().isEmpty())
399                         m_frame.loader().client().updateGlobalHistoryRedirectLinks();
400                 }
401
402                 m_frame.loader().client().updateGlobalHistoryItemForPage();
403             }
404         }
405         // The client redirect replaces the current history item.
406         updateCurrentItem();
407     } else {
408         Frame* parentFrame = m_frame.tree().parent();
409         if (parentFrame && parentFrame->loader().history().currentItem())
410             parentFrame->loader().history().currentItem()->setChildItem(createItem());
411     }
412
413     if (!historyURL.isEmpty() && !needPrivacy) {
414         if (Page* page = m_frame.page())
415             addVisitedLink(*page, historyURL);
416
417         if (!m_frame.loader().documentLoader()->didCreateGlobalHistoryEntry() && m_frame.loader().documentLoader()->unreachableURL().isEmpty() && !m_frame.document()->url().isEmpty())
418             m_frame.loader().client().updateGlobalHistoryRedirectLinks();
419     }
420 }
421
422 void HistoryController::updateForClientRedirect()
423 {
424 #if !LOG_DISABLED
425     if (m_frame.loader().documentLoader())
426         LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame.loader().documentLoader()->title().string().utf8().data());
427 #endif
428
429     // Clear out form data so we don't try to restore it into the incoming page.  Must happen after
430     // webcore has closed the URL and saved away the form state.
431     if (m_currentItem) {
432         m_currentItem->clearDocumentState();
433         m_currentItem->clearScrollPoint();
434     }
435
436     bool needPrivacy = m_frame.settings().privateBrowsingEnabled();
437     const URL& historyURL = m_frame.loader().documentLoader()->urlForHistory();
438
439     if (!historyURL.isEmpty() && !needPrivacy) {
440         if (Page* page = m_frame.page())
441             addVisitedLink(*page, historyURL);
442     }
443 }
444
445 void HistoryController::updateForCommit()
446 {
447     FrameLoader& frameLoader = m_frame.loader();
448 #if !LOG_DISABLED
449     if (frameLoader.documentLoader())
450         LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader.documentLoader()->title().string().utf8().data());
451 #endif
452     FrameLoadType type = frameLoader.loadType();
453     if (isBackForwardLoadType(type)
454         || isReplaceLoadTypeWithProvisionalItem(type)
455         || (isReloadTypeWithProvisionalItem(type) && !frameLoader.provisionalDocumentLoader()->unreachableURL().isEmpty())) {
456         // Once committed, we want to use current item for saving DocState, and
457         // the provisional item for restoring state.
458         // Note previousItem must be set before we close the URL, which will
459         // happen when the data source is made non-provisional below
460         m_frameLoadComplete = false;
461         m_previousItem = m_currentItem;
462         ASSERT(m_provisionalItem);
463         m_currentItem = m_provisionalItem;
464         m_provisionalItem = 0;
465
466         // Tell all other frames in the tree to commit their provisional items and
467         // restore their scroll position.  We'll avoid this frame (which has already
468         // committed) and its children (which will be replaced).
469         m_frame.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     m_frame.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     URL unreachableURL = documentLoader->unreachableURL();
607
608     URL url;
609     URL 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 = m_frame.mainFrame().loader();
800
801     RefPtr<HistoryItem> topItem = frameLoader.history().createItemTree(m_frame, doClip);
802     LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame.loader().documentLoader()->url().string().ascii().data());
803     page->backForward().addItem(topItem.release());
804 }
805
806 void HistoryController::updateCurrentItem()
807 {
808     if (!m_currentItem)
809         return;
810
811     DocumentLoader* documentLoader = m_frame.loader().documentLoader();
812
813     if (!documentLoader->unreachableURL().isEmpty())
814         return;
815
816     if (m_currentItem->url() != documentLoader->url()) {
817         // We ended up on a completely different URL this time, so the HistoryItem
818         // needs to be re-initialized.  Preserve the isTargetItem flag as it is a
819         // property of how this HistoryItem was originally created and is not
820         // dependent on the document.
821         bool isTargetItem = m_currentItem->isTargetItem();
822         m_currentItem->reset();
823         initializeItem(m_currentItem.get());
824         m_currentItem->setIsTargetItem(isTargetItem);
825     } else {
826         // Even if the final URL didn't change, the form data may have changed.
827         m_currentItem->setFormInfoFromRequest(documentLoader->request());
828     }
829 }
830
831 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
832 {
833     if (!m_currentItem)
834         return;
835
836     Page* page = m_frame.page();
837     ASSERT(page);
838
839     // Get a HistoryItem tree for the current frame tree.
840     RefPtr<HistoryItem> topItem = m_frame.mainFrame().loader().history().createItemTree(m_frame, false);
841     
842     // Override data in the current item (created by createItemTree) to reflect
843     // the pushState() arguments.
844     m_currentItem->setTitle(title);
845     m_currentItem->setStateObject(stateObject);
846     m_currentItem->setURLString(urlString);
847
848     page->backForward().addItem(topItem.release());
849
850     if (m_frame.settings().privateBrowsingEnabled())
851         return;
852
853     addVisitedLink(*page, URL(ParsedURLString, urlString));
854     m_frame.loader().client().updateGlobalHistory();
855 }
856
857 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
858 {
859     if (!m_currentItem)
860         return;
861
862     if (!urlString.isEmpty())
863         m_currentItem->setURLString(urlString);
864     m_currentItem->setTitle(title);
865     m_currentItem->setStateObject(stateObject);
866     m_currentItem->setFormData(0);
867     m_currentItem->setFormContentType(String());
868
869     if (m_frame.settings().privateBrowsingEnabled())
870         return;
871
872     ASSERT(m_frame.page());
873     addVisitedLink(*m_frame.page(), URL(ParsedURLString, urlString));
874     m_frame.loader().client().updateGlobalHistory();
875 }
876
877 void HistoryController::replaceCurrentItem(HistoryItem* item)
878 {
879     if (!item)
880         return;
881
882     m_previousItem = nullptr;
883     if (m_provisionalItem)
884         m_provisionalItem = item;
885     else
886         m_currentItem = item;
887 }
888
889 } // namespace WebCore