Mark more heap-allocated classes as fast allocated
[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_FAMILY)
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_FAMILY)
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_FAMILY)
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
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, shouldTreatAsContinuingLoad);
329 }
330
331 void HistoryController::setDefersLoading(bool defer)
332 {
333     m_defersLoading = defer;
334     if (!defer && m_deferredItem) {
335         goToItem(*m_deferredItem, m_deferredFrameLoadType, ShouldTreatAsContinuingLoad::No);
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() ? m_frame.page()->usesEphemeralSession() : true;
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     } else {
399         // The client redirect replaces the current history item.
400         updateCurrentItem();
401     }
402
403     if (!historyURL.isEmpty() && !needPrivacy) {
404         if (Page* page = m_frame.page())
405             addVisitedLink(*page, historyURL);
406
407         if (!frameLoader.documentLoader()->didCreateGlobalHistoryEntry() && frameLoader.documentLoader()->unreachableURL().isEmpty() && !m_frame.document()->url().isEmpty())
408             frameLoader.client().updateGlobalHistoryRedirectLinks();
409     }
410 }
411
412 void HistoryController::updateForRedirectWithLockedBackForwardList()
413 {
414     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() : "");
415     
416     bool needPrivacy = m_frame.page() ? m_frame.page()->usesEphemeralSession() : true;
417     const URL& historyURL = m_frame.loader().documentLoader()->urlForHistory();
418
419     if (m_frame.loader().documentLoader()->isClientRedirect()) {
420         if (!m_currentItem && !m_frame.tree().parent()) {
421             if (!historyURL.isEmpty()) {
422                 updateBackForwardListClippedAtTarget(true);
423                 if (!needPrivacy) {
424                     m_frame.loader().client().updateGlobalHistory();
425                     m_frame.loader().documentLoader()->setDidCreateGlobalHistoryEntry(true);
426                     if (m_frame.loader().documentLoader()->unreachableURL().isEmpty())
427                         m_frame.loader().client().updateGlobalHistoryRedirectLinks();
428                 }
429             }
430         }
431         // The client redirect replaces the current history item.
432         updateCurrentItem();
433     } else {
434         Frame* parentFrame = m_frame.tree().parent();
435         if (parentFrame && parentFrame->loader().history().currentItem())
436             parentFrame->loader().history().currentItem()->setChildItem(createItem());
437     }
438
439     if (!historyURL.isEmpty() && !needPrivacy) {
440         if (Page* page = m_frame.page())
441             addVisitedLink(*page, historyURL);
442
443         if (!m_frame.loader().documentLoader()->didCreateGlobalHistoryEntry() && m_frame.loader().documentLoader()->unreachableURL().isEmpty())
444             m_frame.loader().client().updateGlobalHistoryRedirectLinks();
445     }
446 }
447
448 void HistoryController::updateForClientRedirect()
449 {
450     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() : "");
451
452     // Clear out form data so we don't try to restore it into the incoming page.  Must happen after
453     // webcore has closed the URL and saved away the form state.
454     if (m_currentItem) {
455         m_currentItem->clearDocumentState();
456         m_currentItem->clearScrollPosition();
457     }
458
459     bool needPrivacy = m_frame.page() ? m_frame.page()->usesEphemeralSession() : true;
460     const URL& historyURL = m_frame.loader().documentLoader()->urlForHistory();
461
462     if (!historyURL.isEmpty() && !needPrivacy) {
463         if (Page* page = m_frame.page())
464             addVisitedLink(*page, historyURL);
465     }
466 }
467
468 void HistoryController::updateForCommit()
469 {
470     FrameLoader& frameLoader = m_frame.loader();
471     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() : "");
472
473     FrameLoadType type = frameLoader.loadType();
474     if (isBackForwardLoadType(type)
475         || isReplaceLoadTypeWithProvisionalItem(type)
476         || (isReloadTypeWithProvisionalItem(type) && !frameLoader.provisionalDocumentLoader()->unreachableURL().isEmpty())) {
477         // Once committed, we want to use current item for saving DocState, and
478         // the provisional item for restoring state.
479         // Note previousItem must be set before we close the URL, which will
480         // happen when the data source is made non-provisional below
481
482         // FIXME: https://bugs.webkit.org/show_bug.cgi?id=146842
483         // We should always have a provisional item when committing, but we sometimes don't.
484         // Not having one leads to us not having a m_currentItem later, which is also a terrible known issue.
485         // We should get to the bottom of this.
486         ASSERT(m_provisionalItem);
487         if (m_provisionalItem)
488             setCurrentItem(*m_provisionalItem.get());
489         m_provisionalItem = nullptr;
490
491         // Tell all other frames in the tree to commit their provisional items and
492         // restore their scroll position.  We'll avoid this frame (which has already
493         // committed) and its children (which will be replaced).
494         m_frame.mainFrame().loader().history().recursiveUpdateForCommit();
495     }
496 }
497
498 bool HistoryController::isReplaceLoadTypeWithProvisionalItem(FrameLoadType type)
499 {
500     // Going back to an error page in a subframe can trigger a FrameLoadType::Replace
501     // while m_provisionalItem is set, so we need to commit it.
502     return type == FrameLoadType::Replace && m_provisionalItem;
503 }
504
505 bool HistoryController::isReloadTypeWithProvisionalItem(FrameLoadType type)
506 {
507     return (type == FrameLoadType::Reload || type == FrameLoadType::ReloadFromOrigin) && m_provisionalItem;
508 }
509
510 void HistoryController::recursiveUpdateForCommit()
511 {
512     // The frame that navigated will now have a null provisional item.
513     // Ignore it and its children.
514     if (!m_provisionalItem)
515         return;
516
517     // For each frame that already had the content the item requested (based on
518     // (a matching URL and frame tree snapshot), just restore the scroll position.
519     // Save form state (works from currentItem, since m_frameLoadComplete is true)
520     if (m_currentItem && itemsAreClones(*m_currentItem, m_provisionalItem.get())) {
521         ASSERT(m_frameLoadComplete);
522         saveDocumentState();
523         saveScrollPositionAndViewStateToItem(m_currentItem.get());
524
525         if (FrameView* view = m_frame.view())
526             view->setWasScrolledByUser(false);
527
528         // Now commit the provisional item
529         if (m_provisionalItem)
530             setCurrentItem(*m_provisionalItem.get());
531         m_provisionalItem = nullptr;
532
533         // Restore form state (works from currentItem)
534         restoreDocumentState();
535
536         // Restore the scroll position (we choose to do this rather than going back to the anchor point)
537         restoreScrollPositionAndViewState();
538     }
539
540     // Iterate over the rest of the tree
541     for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
542         child->loader().history().recursiveUpdateForCommit();
543 }
544
545 void HistoryController::updateForSameDocumentNavigation()
546 {
547     if (m_frame.document()->url().isEmpty())
548         return;
549
550     Page* page = m_frame.page();
551     if (!page)
552         return;
553
554     if (page->usesEphemeralSession())
555         return;
556
557     addVisitedLink(*page, m_frame.document()->url());
558     m_frame.mainFrame().loader().history().recursiveUpdateForSameDocumentNavigation();
559
560     if (m_currentItem) {
561         m_currentItem->setURL(m_frame.document()->url());
562         m_frame.loader().client().updateGlobalHistory();
563     }
564 }
565
566 void HistoryController::recursiveUpdateForSameDocumentNavigation()
567 {
568     // The frame that navigated will now have a null provisional item.
569     // Ignore it and its children.
570     if (!m_provisionalItem)
571         return;
572
573     // The provisional item may represent a different pending navigation.
574     // Don't commit it if it isn't a same document navigation.
575     if (m_currentItem && !m_currentItem->shouldDoSameDocumentNavigationTo(*m_provisionalItem))
576         return;
577
578     // Commit the provisional item.
579     if (m_provisionalItem)
580         setCurrentItem(*m_provisionalItem.get());
581     m_provisionalItem = nullptr;
582
583     // Iterate over the rest of the tree.
584     for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
585         child->loader().history().recursiveUpdateForSameDocumentNavigation();
586 }
587
588 void HistoryController::updateForFrameLoadCompleted()
589 {
590     // Even if already complete, we might have set a previous item on a frame that
591     // didn't do any data loading on the past transaction. Make sure to track that
592     // the load is complete so that we use the current item instead.
593     m_frameLoadComplete = true;
594 }
595
596 void HistoryController::setCurrentItem(HistoryItem& item)
597 {
598     m_frameLoadComplete = false;
599     m_previousItem = m_currentItem;
600     m_currentItem = &item;
601 }
602
603 void HistoryController::setCurrentItemTitle(const StringWithDirection& title)
604 {
605     // FIXME: This ignores the title's direction.
606     if (m_currentItem)
607         m_currentItem->setTitle(title.string);
608 }
609
610 bool HistoryController::currentItemShouldBeReplaced() const
611 {
612     // From the HTML5 spec for location.assign():
613     //  "If the browsing context's session history contains only one Document,
614     //   and that was the about:blank Document created when the browsing context
615     //   was created, then the navigation must be done with replacement enabled."
616     return m_currentItem && !m_previousItem && equalIgnoringASCIICase(m_currentItem->urlString(), WTF::blankURL());
617 }
618
619 void HistoryController::clearPreviousItem()
620 {
621     m_previousItem = nullptr;
622     for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
623         child->loader().history().clearPreviousItem();
624 }
625
626 void HistoryController::setProvisionalItem(HistoryItem* item)
627 {
628     m_provisionalItem = item;
629 }
630
631 void HistoryController::initializeItem(HistoryItem& item)
632 {
633     DocumentLoader* documentLoader = m_frame.loader().documentLoader();
634     ASSERT(documentLoader);
635
636     URL unreachableURL = documentLoader->unreachableURL();
637
638     URL url;
639     URL originalURL;
640
641     if (!unreachableURL.isEmpty()) {
642         url = unreachableURL;
643         originalURL = unreachableURL;
644     } else {
645         url = documentLoader->url();
646         originalURL = documentLoader->originalURL();
647     }
648
649     // Frames that have never successfully loaded any content
650     // may have no URL at all. Currently our history code can't
651     // deal with such things, so we nip that in the bud here.
652     // Later we may want to learn to live with nil for URL.
653     // See bug 3368236 and related bugs for more information.
654     if (url.isEmpty()) 
655         url = WTF::blankURL();
656     if (originalURL.isEmpty())
657         originalURL = WTF::blankURL();
658     
659     StringWithDirection title = documentLoader->title();
660
661     item.setURL(url);
662     item.setTarget(m_frame.tree().uniqueName());
663     // FIXME: Should store the title direction as well.
664     item.setTitle(title.string);
665     item.setOriginalURLString(originalURL.string());
666
667     if (!unreachableURL.isEmpty() || documentLoader->response().httpStatusCode() >= 400)
668         item.setLastVisitWasFailure(true);
669
670     item.setShouldOpenExternalURLsPolicy(documentLoader->shouldOpenExternalURLsPolicyToPropagate());
671
672     // Save form state if this is a POST
673     item.setFormInfoFromRequest(documentLoader->request());
674 }
675
676 Ref<HistoryItem> HistoryController::createItem()
677 {
678     Ref<HistoryItem> item = HistoryItem::create();
679     initializeItem(item);
680     
681     // Set the item for which we will save document state
682     setCurrentItem(item);
683     
684     return item;
685 }
686
687 Ref<HistoryItem> HistoryController::createItemTree(Frame& targetFrame, bool clipAtTarget)
688 {
689     Ref<HistoryItem> bfItem = createItem();
690     if (!m_frameLoadComplete)
691         saveScrollPositionAndViewStateToItem(m_previousItem.get());
692
693     if (!clipAtTarget || &m_frame != &targetFrame) {
694         // save frame state for items that aren't loading (khtml doesn't save those)
695         saveDocumentState();
696
697         // clipAtTarget is false for navigations within the same document, so
698         // we should copy the documentSequenceNumber over to the newly create
699         // item.  Non-target items are just clones, and they should therefore
700         // preserve the same itemSequenceNumber.
701         if (m_previousItem) {
702             if (&m_frame != &targetFrame)
703                 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
704             bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
705         }
706
707         for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
708             FrameLoader& childLoader = child->loader();
709             bool hasChildLoaded = childLoader.frameHasLoaded();
710
711             // If the child is a frame corresponding to an <object> element that never loaded,
712             // we don't want to create a history item, because that causes fallback content
713             // to be ignored on reload.
714             
715             if (!(!hasChildLoaded && childLoader.isHostedByObjectElement()))
716                 bfItem->addChildItem(childLoader.history().createItemTree(targetFrame, clipAtTarget));
717         }
718     }
719     // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
720     if (&m_frame == &targetFrame)
721         bfItem->setIsTargetItem(true);
722     return bfItem;
723 }
724
725 // The general idea here is to traverse the frame tree and the item tree in parallel,
726 // tracking whether each frame already has the content the item requests.  If there is
727 // a match, we set the provisional item and recurse.  Otherwise we will reload that
728 // frame and all its kids in recursiveGoToItem.
729 void HistoryController::recursiveSetProvisionalItem(HistoryItem& item, HistoryItem* fromItem)
730 {
731     if (!itemsAreClones(item, fromItem))
732         return;
733
734     // Set provisional item, which will be committed in recursiveUpdateForCommit.
735     m_provisionalItem = &item;
736
737     for (auto& childItem : item.children()) {
738         const String& childFrameName = childItem->target();
739
740         HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
741         ASSERT(fromChildItem);
742         Frame* childFrame = m_frame.tree().child(childFrameName);
743         ASSERT(childFrame);
744
745         childFrame->loader().history().recursiveSetProvisionalItem(childItem, fromChildItem);
746     }
747 }
748
749 // We now traverse the frame tree and item tree a second time, loading frames that
750 // do have the content the item requests.
751 void HistoryController::recursiveGoToItem(HistoryItem& item, HistoryItem* fromItem, FrameLoadType type, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad)
752 {
753     if (!itemsAreClones(item, fromItem)) {
754         m_frame.loader().loadItem(item, type, shouldTreatAsContinuingLoad);
755         return;
756     }
757
758     // Just iterate over the rest, looking for frames to navigate.
759     for (auto& childItem : item.children()) {
760         const String& childFrameName = childItem->target();
761
762         HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
763         ASSERT(fromChildItem);
764         if (Frame* childFrame = m_frame.tree().child(childFrameName))
765             childFrame->loader().history().recursiveGoToItem(childItem, fromChildItem, type, shouldTreatAsContinuingLoad);
766     }
767 }
768
769 // The following logic must be kept in sync with WebKit::WebBackForwardListItem::itemIsClone().
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({ }, 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     ASSERT(m_frame.page());
897     if (m_frame.page()->usesEphemeralSession())
898         return;
899
900     addVisitedLink(*m_frame.page(), URL({ }, 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