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