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