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