Remove PassRefPtr from more of "platform"
[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 "PageGroup.h"
50 #include "ScrollingCoordinator.h"
51 #include "SerializedScriptValue.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, visitedLinkHash(url.string()));
60 }
61
62 HistoryController::HistoryController(Frame& frame)
63     : m_frame(frame)
64     , m_frameLoadComplete(true)
65     , m_defersLoading(false)
66 {
67 }
68
69 HistoryController::~HistoryController()
70 {
71 }
72
73 void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
74 {
75     FrameView* frameView = m_frame.view();
76     if (!item || !frameView)
77         return;
78
79     if (m_frame.document()->pageCacheState() != Document::NotInPageCache)
80         item->setScrollPosition(frameView->cachedScrollPosition());
81     else
82         item->setScrollPosition(frameView->scrollPosition());
83
84 #if PLATFORM(IOS)
85     item->setExposedContentRect(frameView->exposedContentRect());
86     item->setUnobscuredContentRect(frameView->unobscuredContentRect());
87 #endif
88
89     Page* page = m_frame.page();
90     if (page && m_frame.isMainFrame()) {
91         item->setPageScaleFactor(page->pageScaleFactor() / page->viewScaleFactor());
92 #if PLATFORM(IOS)
93         item->setObscuredInset(page->obscuredInset());
94 #endif
95     }
96
97     // FIXME: It would be great to work out a way to put this code in WebCore instead of calling through to the client.
98     m_frame.loader().client().saveViewStateToItem(*item);
99
100     // Notify clients that the HistoryItem has changed.
101     item->notifyChanged();
102 }
103
104 void HistoryController::clearScrollPositionAndViewState()
105 {
106     if (!m_currentItem)
107         return;
108
109     m_currentItem->clearScrollPosition();
110     m_currentItem->setPageScaleFactor(0);
111 }
112
113 /*
114  There is a race condition between the layout and load completion that affects restoring the scroll position.
115  We try to restore the scroll position at both the first layout and upon load completion.
116  
117  1) If first layout happens before the load completes, we want to restore the scroll position then so that the
118  first time we draw the page is already scrolled to the right place, instead of starting at the top and later
119  jumping down.  It is possible that the old scroll position is past the part of the doc laid out so far, in
120  which case the restore silent fails and we will fix it in when we try to restore on doc completion.
121  2) If the layout happens after the load completes, the attempt to restore at load completion time silently
122  fails.  We then successfully restore it when the layout happens.
123 */
124 void HistoryController::restoreScrollPositionAndViewState()
125 {
126     if (!m_frame.loader().stateMachine().committedFirstRealDocumentLoad())
127         return;
128
129     ASSERT(m_currentItem);
130     
131     // FIXME: As the ASSERT attests, it seems we should always have a currentItem here.
132     // One counterexample is <rdar://problem/4917290>
133     // For now, to cover this issue in release builds, there is no technical harm to returning
134     // early and from a user standpoint - as in the above radar - the previous page load failed 
135     // so there *is* no scroll or view state to restore!
136     if (!m_currentItem)
137         return;
138
139     FrameView* view = m_frame.view();
140
141     // FIXME: There is some scrolling related work that needs to happen whenever a page goes into the
142     // page cache and similar work that needs to occur when it comes out. This is where we do the work
143     // that needs to happen when we exit, and the work that needs to happen when we enter is in
144     // Document::setIsInPageCache(bool). It would be nice if there was more symmetry in these spots.
145     // https://bugs.webkit.org/show_bug.cgi?id=98698
146     if (view) {
147         Page* page = m_frame.page();
148         if (page && m_frame.isMainFrame()) {
149             if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator())
150                 scrollingCoordinator->frameViewRootLayerDidChange(*view);
151         }
152     }
153
154     // FIXME: It would be great to work out a way to put this code in WebCore instead of calling
155     // through to the client.
156     m_frame.loader().client().restoreViewState();
157
158 #if !PLATFORM(IOS) && !PLATFORM(EFL)
159     // Don't restore scroll point on iOS as FrameLoaderClient::restoreViewState() does that.
160     if (view && !view->wasScrolledByUser()) {
161         Page* page = m_frame.page();
162         auto desiredScrollPosition = m_currentItem->scrollPosition();
163
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::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     ASSERT(!m_frame.tree().parent());
296     
297     // shouldGoToHistoryItem is a private delegate method. This is needed to fix:
298     // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls
299     // Ultimately, history item navigations should go through the policy delegate. That's covered in:
300     // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate
301     Page* page = m_frame.page();
302     if (!page)
303         return;
304     if (!m_frame.loader().client().shouldGoToHistoryItem(&targetItem))
305         return;
306     if (m_defersLoading) {
307         m_deferredItem = &targetItem;
308         m_deferredFrameLoadType = type;
309         return;
310     }
311
312     // Set the BF cursor before commit, which lets the user quickly click back/forward again.
313     // - plus, it only makes sense for the top level of the operation through the frame tree,
314     // as opposed to happening for some/one of the page commits that might happen soon
315     RefPtr<HistoryItem> currentItem = page->backForward().currentItem();
316     page->backForward().setCurrentItem(&targetItem);
317     m_frame.loader().client().updateGlobalHistoryItemForPage();
318
319     // First set the provisional item of any frames that are not actually navigating.
320     // This must be done before trying to navigate the desired frame, because some
321     // navigations can commit immediately (such as about:blank).  We must be sure that
322     // all frames have provisional items set before the commit.
323     recursiveSetProvisionalItem(targetItem, currentItem.get());
324
325     // Now that all other frames have provisional items, do the actual navigation.
326     recursiveGoToItem(targetItem, currentItem.get(), type);
327 }
328
329 void HistoryController::setDefersLoading(bool defer)
330 {
331     m_defersLoading = defer;
332     if (!defer && m_deferredItem) {
333         goToItem(*m_deferredItem, m_deferredFrameLoadType);
334         m_deferredItem = nullptr;
335     }
336 }
337
338 void HistoryController::updateForBackForwardNavigation()
339 {
340     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() : "");
341
342     // Must grab the current scroll position before disturbing it
343     if (!m_frameLoadComplete)
344         saveScrollPositionAndViewStateToItem(m_previousItem.get());
345
346     // When traversing history, we may end up redirecting to a different URL
347     // this time (e.g., due to cookies).  See http://webkit.org/b/49654.
348     updateCurrentItem();
349 }
350
351 void HistoryController::updateForReload()
352 {
353     LOG(History, "HistoryController %p updateForBackForwardNavigation: 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() : "");
354
355     if (m_currentItem) {
356         PageCache::singleton().remove(*m_currentItem);
357     
358         if (m_frame.loader().loadType() == FrameLoadType::Reload || m_frame.loader().loadType() == FrameLoadType::ReloadFromOrigin)
359             saveScrollPositionAndViewStateToItem(m_currentItem.get());
360
361         // Rebuild the history item tree when reloading as trying to re-associate everything is too error-prone.
362         m_currentItem->clearChildren();
363     }
364
365     // When reloading the page, we may end up redirecting to a different URL
366     // this time (e.g., due to cookies).  See http://webkit.org/b/4072.
367     updateCurrentItem();
368 }
369
370 // There are 3 things you might think of as "history", all of which are handled by these functions.
371 //
372 //     1) Back/forward: The m_currentItem is part of this mechanism.
373 //     2) Global history: Handled by the client.
374 //     3) Visited links: Handled by the PageGroup.
375
376 void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
377 {
378     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());
379
380     FrameLoader& frameLoader = m_frame.loader();
381
382     bool needPrivacy = m_frame.page()->usesEphemeralSession();
383     const URL& historyURL = frameLoader.documentLoader()->urlForHistory();
384
385     if (!frameLoader.documentLoader()->isClientRedirect()) {
386         if (!historyURL.isEmpty()) {
387             if (updateType != UpdateAllExceptBackForwardList)
388                 updateBackForwardListClippedAtTarget(true);
389             if (!needPrivacy) {
390                 frameLoader.client().updateGlobalHistory();
391                 frameLoader.documentLoader()->setDidCreateGlobalHistoryEntry(true);
392                 if (frameLoader.documentLoader()->unreachableURL().isEmpty())
393                     frameLoader.client().updateGlobalHistoryRedirectLinks();
394             }
395
396             m_frame.loader().client().updateGlobalHistoryItemForPage();
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()->usesEphemeralSession();
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                 m_frame.loader().client().updateGlobalHistoryItemForPage();
431             }
432         }
433         // The client redirect replaces the current history item.
434         updateCurrentItem();
435     } else {
436         Frame* parentFrame = m_frame.tree().parent();
437         if (parentFrame && parentFrame->loader().history().currentItem())
438             parentFrame->loader().history().currentItem()->setChildItem(createItem());
439     }
440
441     if (!historyURL.isEmpty() && !needPrivacy) {
442         if (Page* page = m_frame.page())
443             addVisitedLink(*page, historyURL);
444
445         if (!m_frame.loader().documentLoader()->didCreateGlobalHistoryEntry() && m_frame.loader().documentLoader()->unreachableURL().isEmpty() && !m_frame.document()->url().isEmpty())
446             m_frame.loader().client().updateGlobalHistoryRedirectLinks();
447     }
448 }
449
450 void HistoryController::updateForClientRedirect()
451 {
452     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() : "");
453
454     // Clear out form data so we don't try to restore it into the incoming page.  Must happen after
455     // webcore has closed the URL and saved away the form state.
456     if (m_currentItem) {
457         m_currentItem->clearDocumentState();
458         m_currentItem->clearScrollPosition();
459     }
460
461     bool needPrivacy = m_frame.page()->usesEphemeralSession();
462     const URL& historyURL = m_frame.loader().documentLoader()->urlForHistory();
463
464     if (!historyURL.isEmpty() && !needPrivacy) {
465         if (Page* page = m_frame.page())
466             addVisitedLink(*page, historyURL);
467     }
468 }
469
470 void HistoryController::updateForCommit()
471 {
472     FrameLoader& frameLoader = m_frame.loader();
473     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() : "");
474
475     FrameLoadType type = frameLoader.loadType();
476     if (isBackForwardLoadType(type)
477         || isReplaceLoadTypeWithProvisionalItem(type)
478         || (isReloadTypeWithProvisionalItem(type) && !frameLoader.provisionalDocumentLoader()->unreachableURL().isEmpty())) {
479         // Once committed, we want to use current item for saving DocState, and
480         // the provisional item for restoring state.
481         // Note previousItem must be set before we close the URL, which will
482         // happen when the data source is made non-provisional below
483
484         // FIXME: https://bugs.webkit.org/show_bug.cgi?id=146842
485         // We should always have a provisional item when committing, but we sometimes don't.
486         // Not having one leads to us not having a m_currentItem later, which is also a terrible known issue.
487         // We should get to the bottom of this.
488         ASSERT(m_provisionalItem);
489         setCurrentItem(m_provisionalItem.get());
490         m_provisionalItem = nullptr;
491
492         // Tell all other frames in the tree to commit their provisional items and
493         // restore their scroll position.  We'll avoid this frame (which has already
494         // committed) and its children (which will be replaced).
495         m_frame.mainFrame().loader().history().recursiveUpdateForCommit();
496     }
497 }
498
499 bool HistoryController::isReplaceLoadTypeWithProvisionalItem(FrameLoadType type)
500 {
501     // Going back to an error page in a subframe can trigger a FrameLoadType::Replace
502     // while m_provisionalItem is set, so we need to commit it.
503     return type == FrameLoadType::Replace && m_provisionalItem;
504 }
505
506 bool HistoryController::isReloadTypeWithProvisionalItem(FrameLoadType type)
507 {
508     return (type == FrameLoadType::Reload || type == FrameLoadType::ReloadFromOrigin) && m_provisionalItem;
509 }
510
511 void HistoryController::recursiveUpdateForCommit()
512 {
513     // The frame that navigated will now have a null provisional item.
514     // Ignore it and its children.
515     if (!m_provisionalItem)
516         return;
517
518     // For each frame that already had the content the item requested (based on
519     // (a matching URL and frame tree snapshot), just restore the scroll position.
520     // Save form state (works from currentItem, since m_frameLoadComplete is true)
521     if (m_currentItem && itemsAreClones(*m_currentItem, m_provisionalItem.get())) {
522         ASSERT(m_frameLoadComplete);
523         saveDocumentState();
524         saveScrollPositionAndViewStateToItem(m_currentItem.get());
525
526         if (FrameView* view = m_frame.view())
527             view->setWasScrolledByUser(false);
528
529         // Now commit the provisional item
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     if (m_frame.page()->usesEphemeralSession())
551         return;
552
553     Page* page = m_frame.page();
554     if (!page)
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     setCurrentItem(m_provisionalItem.get());
580     m_provisionalItem = nullptr;
581
582     // Iterate over the rest of the tree.
583     for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
584         child->loader().history().recursiveUpdateForSameDocumentNavigation();
585 }
586
587 void HistoryController::updateForFrameLoadCompleted()
588 {
589     // Even if already complete, we might have set a previous item on a frame that
590     // didn't do any data loading on the past transaction. Make sure to track that
591     // the load is complete so that we use the current item instead.
592     m_frameLoadComplete = true;
593 }
594
595 void HistoryController::setCurrentItem(HistoryItem* item)
596 {
597     m_frameLoadComplete = false;
598     m_previousItem = m_currentItem;
599     m_currentItem = item;
600 }
601
602 void HistoryController::setCurrentItemTitle(const StringWithDirection& title)
603 {
604     // FIXME: This ignores the title's direction.
605     if (m_currentItem)
606         m_currentItem->setTitle(title.string);
607 }
608
609 bool HistoryController::currentItemShouldBeReplaced() const
610 {
611     // From the HTML5 spec for location.assign():
612     //  "If the browsing context's session history contains only one Document,
613     //   and that was the about:blank Document created when the browsing context
614     //   was created, then the navigation must be done with replacement enabled."
615     return m_currentItem && !m_previousItem && equalIgnoringASCIICase(m_currentItem->urlString(), blankURL());
616 }
617
618 void HistoryController::clearPreviousItem()
619 {
620     m_previousItem = nullptr;
621     for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
622         child->loader().history().clearPreviousItem();
623 }
624
625 void HistoryController::setProvisionalItem(HistoryItem* item)
626 {
627     m_provisionalItem = item;
628 }
629
630 void HistoryController::initializeItem(HistoryItem& item)
631 {
632     DocumentLoader* documentLoader = m_frame.loader().documentLoader();
633     ASSERT(documentLoader);
634
635     URL unreachableURL = documentLoader->unreachableURL();
636
637     URL url;
638     URL originalURL;
639
640     if (!unreachableURL.isEmpty()) {
641         url = unreachableURL;
642         originalURL = unreachableURL;
643     } else {
644         url = documentLoader->url();
645         originalURL = documentLoader->originalURL();
646     }
647
648     // Frames that have never successfully loaded any content
649     // may have no URL at all. Currently our history code can't
650     // deal with such things, so we nip that in the bud here.
651     // Later we may want to learn to live with nil for URL.
652     // See bug 3368236 and related bugs for more information.
653     if (url.isEmpty()) 
654         url = blankURL();
655     if (originalURL.isEmpty())
656         originalURL = blankURL();
657     
658     StringWithDirection title = documentLoader->title();
659
660     item.setURL(url);
661     item.setTarget(m_frame.tree().uniqueName());
662     // FIXME: Should store the title direction as well.
663     item.setTitle(title.string);
664     item.setOriginalURLString(originalURL.string());
665
666     if (!unreachableURL.isEmpty() || documentLoader->response().httpStatusCode() >= 400)
667         item.setLastVisitWasFailure(true);
668
669     item.setShouldOpenExternalURLsPolicy(documentLoader->shouldOpenExternalURLsPolicyToPropagate());
670
671     // Save form state if this is a POST
672     item.setFormInfoFromRequest(documentLoader->request());
673 }
674
675 Ref<HistoryItem> HistoryController::createItem()
676 {
677     Ref<HistoryItem> item = HistoryItem::create();
678     initializeItem(item);
679     
680     // Set the item for which we will save document state
681     setCurrentItem(item.ptr());
682     
683     return item;
684 }
685
686 Ref<HistoryItem> HistoryController::createItemTree(Frame& targetFrame, bool clipAtTarget)
687 {
688     Ref<HistoryItem> bfItem = createItem();
689     if (!m_frameLoadComplete)
690         saveScrollPositionAndViewStateToItem(m_previousItem.get());
691
692     if (!clipAtTarget || &m_frame != &targetFrame) {
693         // save frame state for items that aren't loading (khtml doesn't save those)
694         saveDocumentState();
695
696         // clipAtTarget is false for navigations within the same document, so
697         // we should copy the documentSequenceNumber over to the newly create
698         // item.  Non-target items are just clones, and they should therefore
699         // preserve the same itemSequenceNumber.
700         if (m_previousItem) {
701             if (&m_frame != &targetFrame)
702                 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
703             bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
704         }
705
706         for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
707             FrameLoader& childLoader = child->loader();
708             bool hasChildLoaded = childLoader.frameHasLoaded();
709
710             // If the child is a frame corresponding to an <object> element that never loaded,
711             // we don't want to create a history item, because that causes fallback content
712             // to be ignored on reload.
713             
714             if (!(!hasChildLoaded && childLoader.isHostedByObjectElement()))
715                 bfItem->addChildItem(childLoader.history().createItemTree(targetFrame, clipAtTarget));
716         }
717     }
718     // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
719     if (&m_frame == &targetFrame)
720         bfItem->setIsTargetItem(true);
721     return bfItem;
722 }
723
724 // The general idea here is to traverse the frame tree and the item tree in parallel,
725 // tracking whether each frame already has the content the item requests.  If there is
726 // a match, we set the provisional item and recurse.  Otherwise we will reload that
727 // frame and all its kids in recursiveGoToItem.
728 void HistoryController::recursiveSetProvisionalItem(HistoryItem& item, HistoryItem* fromItem)
729 {
730     if (!itemsAreClones(item, fromItem))
731         return;
732
733     // Set provisional item, which will be committed in recursiveUpdateForCommit.
734     m_provisionalItem = &item;
735
736     for (auto& childItem : item.children()) {
737         const String& childFrameName = childItem->target();
738
739         HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
740         ASSERT(fromChildItem);
741         Frame* childFrame = m_frame.tree().child(childFrameName);
742         ASSERT(childFrame);
743
744         childFrame->loader().history().recursiveSetProvisionalItem(childItem, fromChildItem);
745     }
746 }
747
748 // We now traverse the frame tree and item tree a second time, loading frames that
749 // do have the content the item requests.
750 void HistoryController::recursiveGoToItem(HistoryItem& item, HistoryItem* fromItem, FrameLoadType type)
751 {
752     if (!itemsAreClones(item, fromItem)) {
753         m_frame.loader().loadItem(item, type);
754         return;
755     }
756
757     // Just iterate over the rest, looking for frames to navigate.
758     for (auto& childItem : item.children()) {
759         const String& childFrameName = childItem->target();
760
761         HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
762         ASSERT(fromChildItem);
763         if (Frame* childFrame = m_frame.tree().child(childFrameName))
764             childFrame->loader().history().recursiveGoToItem(childItem, fromChildItem, type);
765     }
766 }
767
768 bool HistoryController::itemsAreClones(HistoryItem& item1, HistoryItem* item2) const
769 {
770     // If the item we're going to is a clone of the item we're at, then we do
771     // not need to load it again.  The current frame tree and the frame tree
772     // snapshot in the item have to match.
773     // Note: Some clients treat a navigation to the current history item as
774     // a reload.  Thus, if item1 and item2 are the same, we need to create a
775     // new document and should not consider them clones.
776     // (See http://webkit.org/b/35532 for details.)
777     return item2
778         && &item1 != item2
779         && item1.itemSequenceNumber() == item2->itemSequenceNumber()
780         && currentFramesMatchItem(&item1)
781         && item2->hasSameFrames(item1);
782 }
783
784 // Helper method that determines whether the current frame tree matches given history item's.
785 bool HistoryController::currentFramesMatchItem(HistoryItem* item) const
786 {
787     if ((!m_frame.tree().uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame.tree().uniqueName() != item->target())
788         return false;
789         
790     const auto& childItems = item->children();
791     if (childItems.size() != m_frame.tree().childCount())
792         return false;
793     
794     for (auto& item : childItems) {
795         if (!m_frame.tree().child(item->target()))
796             return false;
797     }
798     
799     return true;
800 }
801
802 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
803 {
804     // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.  
805     // The item that was the target of the user's navigation is designated as the "targetItem".  
806     // When this function is called with doClip=true we're able to create the whole tree except for the target's children, 
807     // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
808
809     Page* page = m_frame.page();
810     if (!page)
811         return;
812
813     if (m_frame.loader().documentLoader()->urlForHistory().isEmpty())
814         return;
815
816     FrameLoader& frameLoader = m_frame.mainFrame().loader();
817
818     Ref<HistoryItem> topItem = frameLoader.history().createItemTree(m_frame, doClip);
819     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());
820
821     page->backForward().addItem(WTFMove(topItem));
822 }
823
824 void HistoryController::updateCurrentItem()
825 {
826     if (!m_currentItem)
827         return;
828
829     DocumentLoader* documentLoader = m_frame.loader().documentLoader();
830
831     if (!documentLoader->unreachableURL().isEmpty())
832         return;
833
834     if (m_currentItem->url() != documentLoader->url()) {
835         // We ended up on a completely different URL this time, so the HistoryItem
836         // needs to be re-initialized.  Preserve the isTargetItem flag as it is a
837         // property of how this HistoryItem was originally created and is not
838         // dependent on the document.
839         bool isTargetItem = m_currentItem->isTargetItem();
840         m_currentItem->reset();
841         initializeItem(*m_currentItem);
842         m_currentItem->setIsTargetItem(isTargetItem);
843     } else {
844         // Even if the final URL didn't change, the form data may have changed.
845         m_currentItem->setFormInfoFromRequest(documentLoader->request());
846     }
847 }
848
849 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
850 {
851     if (!m_currentItem)
852         return;
853
854     Page* page = m_frame.page();
855     ASSERT(page);
856
857     // Get a HistoryItem tree for the current frame tree.
858     Ref<HistoryItem> topItem = m_frame.mainFrame().loader().history().createItemTree(m_frame, false);
859     
860     // Override data in the current item (created by createItemTree) to reflect
861     // the pushState() arguments.
862     m_currentItem->setTitle(title);
863     m_currentItem->setStateObject(stateObject);
864     m_currentItem->setURLString(urlString);
865
866     LOG(History, "HistoryController %p pushState: Adding top item %p, setting url of current item %p to %s", this, topItem.ptr(), m_currentItem.get(), urlString.ascii().data());
867
868     page->backForward().addItem(WTFMove(topItem));
869
870     if (m_frame.page()->usesEphemeralSession())
871         return;
872
873     addVisitedLink(*page, URL(ParsedURLString, urlString));
874     m_frame.loader().client().updateGlobalHistory();
875 }
876
877 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
878 {
879     if (!m_currentItem)
880         return;
881
882     LOG(History, "HistoryController %p replaceState: Setting url of current item %p to %s", this, m_currentItem.get(), urlString.ascii().data());
883
884     if (!urlString.isEmpty())
885         m_currentItem->setURLString(urlString);
886     m_currentItem->setTitle(title);
887     m_currentItem->setStateObject(stateObject);
888     m_currentItem->setFormData(nullptr);
889     m_currentItem->setFormContentType(String());
890
891     if (m_frame.page()->usesEphemeralSession())
892         return;
893
894     ASSERT(m_frame.page());
895     addVisitedLink(*m_frame.page(), URL(ParsedURLString, urlString));
896     m_frame.loader().client().updateGlobalHistory();
897 }
898
899 void HistoryController::replaceCurrentItem(HistoryItem* item)
900 {
901     if (!item)
902         return;
903
904     m_previousItem = nullptr;
905     if (m_provisionalItem)
906         m_provisionalItem = item;
907     else
908         m_currentItem = item;
909 }
910
911 } // namespace WebCore