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