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