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/)
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
32 #include "HistoryController.h"
34 #include "BackForwardController.h"
35 #include "CachedPage.h"
36 #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"
46 #include "PageCache.h"
47 #include "PageGroup.h"
49 #include <wtf/text/CString.h>
51 #if USE(PLATFORM_STRATEGIES)
52 #include "PlatformStrategies.h"
53 #include "VisitedLinkStrategy.h"
58 static inline void addVisitedLink(Page* page, const KURL& url)
60 #if USE(PLATFORM_STRATEGIES)
61 platformStrategies()->visitedLinkStrategy()->addVisitedLink(page, visitedLinkHash(url.string().characters(), url.string().length()));
63 page->group().addVisitedLink(url);
67 HistoryController::HistoryController(Frame* frame)
69 , m_frameLoadComplete(true)
73 HistoryController::~HistoryController()
77 void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item)
79 if (!item || !m_frame->view())
82 item->setScrollPoint(m_frame->view()->scrollPosition());
83 item->setPageScaleFactor(m_frame->pageScaleFactor());
85 // FIXME: It would be great to work out a way to put this code in WebCore instead of calling through to the client.
86 m_frame->loader()->client()->saveViewStateToItem(item);
90 There is a race condition between the layout and load completion that affects restoring the scroll position.
91 We try to restore the scroll position at both the first layout and upon load completion.
93 1) If first layout happens before the load completes, we want to restore the scroll position then so that the
94 first time we draw the page is already scrolled to the right place, instead of starting at the top and later
95 jumping down. It is possible that the old scroll position is past the part of the doc laid out so far, in
96 which case the restore silent fails and we will fix it in when we try to restore on doc completion.
97 2) If the layout happens after the load completes, the attempt to restore at load completion time silently
98 fails. We then successfully restore it when the layout happens.
100 void HistoryController::restoreScrollPositionAndViewState()
102 if (!m_frame->loader()->stateMachine()->committedFirstRealDocumentLoad())
105 ASSERT(m_currentItem);
107 // FIXME: As the ASSERT attests, it seems we should always have a currentItem here.
108 // One counterexample is <rdar://problem/4917290>
109 // For now, to cover this issue in release builds, there is no technical harm to returning
110 // early and from a user standpoint - as in the above radar - the previous page load failed
111 // so there *is* no scroll or view state to restore!
115 // FIXME: It would be great to work out a way to put this code in WebCore instead of calling
116 // through to the client. It's currently used only for the PDF view on Mac.
117 m_frame->loader()->client()->restoreViewState();
119 if (FrameView* view = m_frame->view()) {
120 if (!view->wasScrolledByUser()) {
121 view->setScrollPosition(m_currentItem->scrollPoint());
122 m_frame->scalePage(m_currentItem->pageScaleFactor(), m_currentItem->scrollPoint());
127 void HistoryController::updateBackForwardListForFragmentScroll()
129 updateBackForwardListClippedAtTarget(false);
132 void HistoryController::saveDocumentState()
134 // FIXME: Reading this bit of FrameLoader state here is unfortunate. I need to study
135 // this more to see if we can remove this dependency.
136 if (m_frame->loader()->stateMachine()->creatingInitialEmptyDocument())
139 // For a standard page load, we will have a previous item set, which will be used to
140 // store the form state. However, in some cases we will have no previous item, and
141 // the current item is the right place to save the state. One example is when we
142 // detach a bunch of frames because we are navigating from a site with frames to
143 // another site. Another is when saving the frame state of a frame that is not the
144 // target of the current navigation (if we even decide to save with that granularity).
146 // Because of previousItem's "masking" of currentItem for this purpose, it's important
147 // that we keep track of the end of a page transition with m_frameLoadComplete. We
148 // leverage the checkLoadComplete recursion to achieve this goal.
150 HistoryItem* item = m_frameLoadComplete ? m_currentItem.get() : m_previousItem.get();
154 Document* document = m_frame->document();
157 if (item->isCurrentDocument(document)) {
158 LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame->tree()->uniqueName().string().utf8().data(), item);
159 item->setDocumentState(document->formElementsState());
163 // Walk the frame tree, telling all frames to save their form state into their current
165 void HistoryController::saveDocumentAndScrollState()
167 for (Frame* frame = m_frame; frame; frame = frame->tree()->traverseNext(m_frame)) {
168 frame->loader()->history()->saveDocumentState();
169 frame->loader()->history()->saveScrollPositionAndViewStateToItem(frame->loader()->history()->currentItem());
173 void HistoryController::restoreDocumentState()
175 Document* doc = m_frame->document();
177 HistoryItem* itemToRestore = 0;
179 switch (m_frame->loader()->loadType()) {
180 case FrameLoadTypeReload:
181 case FrameLoadTypeReloadFromOrigin:
182 case FrameLoadTypeSame:
183 case FrameLoadTypeReplace:
185 case FrameLoadTypeBack:
186 case FrameLoadTypeBackWMLDeckNotAccessible:
187 case FrameLoadTypeForward:
188 case FrameLoadTypeIndexedBackForward:
189 case FrameLoadTypeRedirectWithLockedBackForwardList:
190 case FrameLoadTypeStandard:
191 itemToRestore = m_currentItem.get();
197 LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame->tree()->uniqueName().string().utf8().data(), itemToRestore);
198 doc->setStateForNewFormElements(itemToRestore->documentState());
201 void HistoryController::invalidateCurrentItemCachedPage()
203 // When we are pre-commit, the currentItem is where the pageCache data resides
204 CachedPage* cachedPage = pageCache()->get(currentItem());
206 // FIXME: This is a grotesque hack to fix <rdar://problem/4059059> Crash in RenderFlow::detach
207 // Somehow the PageState object is not properly updated, and is holding onto a stale document.
208 // Both Xcode and FileMaker see this crash, Safari does not.
210 ASSERT(!cachedPage || cachedPage->document() == m_frame->document());
211 if (cachedPage && cachedPage->document() == m_frame->document()) {
212 cachedPage->document()->setInPageCache(false);
217 pageCache()->remove(currentItem());
220 // Main funnel for navigating to a previous location (back/forward, non-search snap-back)
221 // This includes recursion to handle loading into framesets properly
222 void HistoryController::goToItem(HistoryItem* targetItem, FrameLoadType type)
224 ASSERT(!m_frame->tree()->parent());
226 // shouldGoToHistoryItem is a private delegate method. This is needed to fix:
227 // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls
228 // Ultimately, history item navigations should go through the policy delegate. That's covered in:
229 // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate
230 Page* page = m_frame->page();
233 if (!m_frame->loader()->client()->shouldGoToHistoryItem(targetItem))
236 // Set the BF cursor before commit, which lets the user quickly click back/forward again.
237 // - plus, it only makes sense for the top level of the operation through the frametree,
238 // as opposed to happening for some/one of the page commits that might happen soon
239 HistoryItem* currentItem = page->backForward()->currentItem();
240 page->backForward()->setCurrentItem(targetItem);
241 Settings* settings = m_frame->settings();
242 page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : targetItem);
244 // First set the provisional item of any frames that are not actually navigating.
245 // This must be done before trying to navigate the desired frame, because some
246 // navigations can commit immediately (such as about:blank). We must be sure that
247 // all frames have provisional items set before the commit.
248 recursiveSetProvisionalItem(targetItem, currentItem, type);
249 // Now that all other frames have provisional items, do the actual navigation.
250 recursiveGoToItem(targetItem, currentItem, type);
253 void HistoryController::updateForBackForwardNavigation()
256 if (m_frame->loader()->documentLoader())
257 LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
260 // Must grab the current scroll position before disturbing it
261 if (!m_frameLoadComplete)
262 saveScrollPositionAndViewStateToItem(m_previousItem.get());
264 // When traversing history, we may end up redirecting to a different URL
265 // this time (e.g., due to cookies). See http://webkit.org/b/49654.
269 void HistoryController::updateForReload()
272 if (m_frame->loader()->documentLoader())
273 LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
277 pageCache()->remove(m_currentItem.get());
279 if (m_frame->loader()->loadType() == FrameLoadTypeReload || m_frame->loader()->loadType() == FrameLoadTypeReloadFromOrigin)
280 saveScrollPositionAndViewStateToItem(m_currentItem.get());
283 // When reloading the page, we may end up redirecting to a different URL
284 // this time (e.g., due to cookies). See http://webkit.org/b/4072.
288 // There are 3 things you might think of as "history", all of which are handled by these functions.
290 // 1) Back/forward: The m_currentItem is part of this mechanism.
291 // 2) Global history: Handled by the client.
292 // 3) Visited links: Handled by the PageGroup.
294 void HistoryController::updateForStandardLoad(HistoryUpdateType updateType)
296 LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data());
298 FrameLoader* frameLoader = m_frame->loader();
300 Settings* settings = m_frame->settings();
301 bool needPrivacy = !settings || settings->privateBrowsingEnabled();
302 const KURL& historyURL = frameLoader->documentLoader()->urlForHistory();
304 if (!frameLoader->documentLoader()->isClientRedirect()) {
305 if (!historyURL.isEmpty()) {
306 if (updateType != UpdateAllExceptBackForwardList)
307 updateBackForwardListClippedAtTarget(true);
309 frameLoader->client()->updateGlobalHistory();
310 frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true);
311 if (frameLoader->documentLoader()->unreachableURL().isEmpty())
312 frameLoader->client()->updateGlobalHistoryRedirectLinks();
314 if (Page* page = m_frame->page())
315 page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForward()->currentItem());
318 // The client redirect replaces the current history item.
322 if (!historyURL.isEmpty() && !needPrivacy) {
323 if (Page* page = m_frame->page())
324 addVisitedLink(page, historyURL);
326 if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !m_frame->document()->url().isEmpty())
327 frameLoader->client()->updateGlobalHistoryRedirectLinks();
331 void HistoryController::updateForRedirectWithLockedBackForwardList()
334 if (m_frame->loader()->documentLoader())
335 LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
338 Settings* settings = m_frame->settings();
339 bool needPrivacy = !settings || settings->privateBrowsingEnabled();
340 const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
342 if (m_frame->loader()->documentLoader()->isClientRedirect()) {
343 if (!m_currentItem && !m_frame->tree()->parent()) {
344 if (!historyURL.isEmpty()) {
345 updateBackForwardListClippedAtTarget(true);
347 m_frame->loader()->client()->updateGlobalHistory();
348 m_frame->loader()->documentLoader()->setDidCreateGlobalHistoryEntry(true);
349 if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty())
350 m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
352 if (Page* page = m_frame->page())
353 page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForward()->currentItem());
356 // The client redirect replaces the current history item.
359 Frame* parentFrame = m_frame->tree()->parent();
360 if (parentFrame && parentFrame->loader()->history()->m_currentItem)
361 parentFrame->loader()->history()->m_currentItem->setChildItem(createItem());
364 if (!historyURL.isEmpty() && !needPrivacy) {
365 if (Page* page = m_frame->page())
366 addVisitedLink(page, historyURL);
368 if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->document()->url().isEmpty())
369 m_frame->loader()->client()->updateGlobalHistoryRedirectLinks();
373 void HistoryController::updateForClientRedirect()
376 if (m_frame->loader()->documentLoader())
377 LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame->loader()->documentLoader()->title().utf8().data());
380 // Clear out form data so we don't try to restore it into the incoming page. Must happen after
381 // webcore has closed the URL and saved away the form state.
383 m_currentItem->clearDocumentState();
384 m_currentItem->clearScrollPoint();
387 Settings* settings = m_frame->settings();
388 bool needPrivacy = !settings || settings->privateBrowsingEnabled();
389 const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory();
391 if (!historyURL.isEmpty() && !needPrivacy) {
392 if (Page* page = m_frame->page())
393 addVisitedLink(page, historyURL);
397 void HistoryController::updateForCommit()
399 FrameLoader* frameLoader = m_frame->loader();
401 if (frameLoader->documentLoader())
402 LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().utf8().data());
404 FrameLoadType type = frameLoader->loadType();
405 if (isBackForwardLoadType(type) ||
406 ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) {
407 // Once committed, we want to use current item for saving DocState, and
408 // the provisional item for restoring state.
409 // Note previousItem must be set before we close the URL, which will
410 // happen when the data source is made non-provisional below
411 m_frameLoadComplete = false;
412 m_previousItem = m_currentItem;
413 ASSERT(m_provisionalItem);
414 m_currentItem = m_provisionalItem;
415 m_provisionalItem = 0;
417 // Tell all other frames in the tree to commit their provisional items and
418 // restore their scroll position. We'll avoid this frame (which has already
419 // committed) and its children (which will be replaced).
420 Page* page = m_frame->page();
422 page->mainFrame()->loader()->history()->recursiveUpdateForCommit();
426 void HistoryController::recursiveUpdateForCommit()
428 // The frame that navigated will now have a null provisional item.
429 // Ignore it and its children.
430 if (!m_provisionalItem)
433 // For each frame that already had the content the item requested (based on
434 // (a matching URL and frame tree snapshot), just restore the scroll position.
435 // Save form state (works from currentItem, since m_frameLoadComplete is true)
436 ASSERT(m_frameLoadComplete);
438 saveScrollPositionAndViewStateToItem(m_currentItem.get());
440 if (FrameView* view = m_frame->view())
441 view->setWasScrolledByUser(false);
443 // Now commit the provisional item
444 m_frameLoadComplete = false;
445 m_previousItem = m_currentItem;
446 m_currentItem = m_provisionalItem;
447 m_provisionalItem = 0;
449 // Restore form state (works from currentItem)
450 restoreDocumentState();
452 // Restore the scroll position (we choose to do this rather than going back to the anchor point)
453 restoreScrollPositionAndViewState();
455 // Iterate over the rest of the tree
456 for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
457 child->loader()->history()->recursiveUpdateForCommit();
460 void HistoryController::updateForSameDocumentNavigation()
462 if (m_frame->document()->url().isEmpty())
465 Settings* settings = m_frame->settings();
466 if (!settings || settings->privateBrowsingEnabled())
469 Page* page = m_frame->page();
473 addVisitedLink(page, m_frame->document()->url());
476 void HistoryController::updateForFrameLoadCompleted()
478 // Even if already complete, we might have set a previous item on a frame that
479 // didn't do any data loading on the past transaction. Make sure to track that
480 // the load is complete so that we use the current item instead.
481 m_frameLoadComplete = true;
484 void HistoryController::setCurrentItem(HistoryItem* item)
486 m_frameLoadComplete = false;
487 m_previousItem = m_currentItem;
488 m_currentItem = item;
491 void HistoryController::setCurrentItemTitle(const String& title)
494 m_currentItem->setTitle(title);
497 bool HistoryController::currentItemShouldBeReplaced() const
499 // From the HTML5 spec for location.assign():
500 // "If the browsing context's session history contains only one Document,
501 // and that was the about:blank Document created when the browsing context
502 // was created, then the navigation must be done with replacement enabled."
503 return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
506 void HistoryController::setProvisionalItem(HistoryItem* item)
508 m_provisionalItem = item;
511 void HistoryController::initializeItem(HistoryItem* item)
513 DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
514 ASSERT(documentLoader);
516 KURL unreachableURL = documentLoader->unreachableURL();
521 if (!unreachableURL.isEmpty()) {
522 url = unreachableURL;
523 originalURL = unreachableURL;
525 url = documentLoader->url();
526 originalURL = documentLoader->originalURL();
529 // Frames that have never successfully loaded any content
530 // may have no URL at all. Currently our history code can't
531 // deal with such things, so we nip that in the bud here.
532 // Later we may want to learn to live with nil for URL.
533 // See bug 3368236 and related bugs for more information.
536 if (originalURL.isEmpty())
537 originalURL = blankURL();
539 Frame* parentFrame = m_frame->tree()->parent();
540 String parent = parentFrame ? parentFrame->tree()->uniqueName() : "";
541 String title = documentLoader->title();
544 item->setTarget(m_frame->tree()->uniqueName());
545 item->setParent(parent);
546 item->setTitle(title);
547 item->setOriginalURLString(originalURL.string());
549 if (!unreachableURL.isEmpty() || documentLoader->response().httpStatusCode() >= 400)
550 item->setLastVisitWasFailure(true);
552 // Save form state if this is a POST
553 item->setFormInfoFromRequest(documentLoader->request());
556 PassRefPtr<HistoryItem> HistoryController::createItem()
558 RefPtr<HistoryItem> item = HistoryItem::create();
559 initializeItem(item.get());
561 // Set the item for which we will save document state
562 m_frameLoadComplete = false;
563 m_previousItem = m_currentItem;
564 m_currentItem = item;
566 return item.release();
569 PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
571 RefPtr<HistoryItem> bfItem = createItem();
572 if (!m_frameLoadComplete)
573 saveScrollPositionAndViewStateToItem(m_previousItem.get());
575 if (!clipAtTarget || m_frame != targetFrame) {
576 // save frame state for items that aren't loading (khtml doesn't save those)
579 // clipAtTarget is false for navigations within the same document, so
580 // we should copy the documentSequenceNumber over to the newly create
581 // item. Non-target items are just clones, and they should therefore
582 // preserve the same itemSequenceNumber.
583 if (m_previousItem) {
584 if (m_frame != targetFrame)
585 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
586 bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
589 for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
590 FrameLoader* childLoader = child->loader();
591 bool hasChildLoaded = childLoader->frameHasLoaded();
593 // If the child is a frame corresponding to an <object> element that never loaded,
594 // we don't want to create a history item, because that causes fallback content
595 // to be ignored on reload.
597 if (!(!hasChildLoaded && childLoader->isHostedByObjectElement()))
598 bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget));
601 // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
602 if (m_frame == targetFrame)
603 bfItem->setIsTargetItem(true);
607 // The general idea here is to traverse the frame tree and the item tree in parallel,
608 // tracking whether each frame already has the content the item requests. If there is
609 // a match, we set the provisional item and recurse. Otherwise we will reload that
610 // frame and all its kids in recursiveGoToItem.
611 void HistoryController::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
616 if (itemsAreClones(item, fromItem)) {
617 // Set provisional item, which will be committed in recursiveUpdateForCommit.
618 m_provisionalItem = item;
620 const HistoryItemVector& childItems = item->children();
622 int size = childItems.size();
624 // Sanity checks for http://webkit.org/b/52819.
626 // fromItem should have same number of children according to hasSameFrames,
627 // but crash dumps suggest it might have 0.
628 if (!fromItem->children().size())
630 // itemsAreClones checked fromItem->hasSameFrames(item). Check vice versa.
631 if (!item->hasSameFrames(fromItem))
635 for (int i = 0; i < size; ++i) {
636 String childFrameName = childItems[i]->target();
637 HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
638 ASSERT(fromChildItem);
639 Frame* childFrame = m_frame->tree()->child(childFrameName);
641 childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem, type);
646 // We now traverse the frame tree and item tree a second time, loading frames that
647 // do have the content the item requests.
648 void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
653 if (itemsAreClones(item, fromItem)) {
654 // Just iterate over the rest, looking for frames to navigate.
655 const HistoryItemVector& childItems = item->children();
657 int size = childItems.size();
658 for (int i = 0; i < size; ++i) {
659 String childFrameName = childItems[i]->target();
660 HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
661 ASSERT(fromChildItem);
662 Frame* childFrame = m_frame->tree()->child(childFrameName);
664 childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type);
667 m_frame->loader()->loadItem(item, type);
671 bool HistoryController::itemsAreClones(HistoryItem* item1, HistoryItem* item2) const
673 // It appears that one of the items can be null in release builds, leading
674 // to the crashes seen in http://webkit.org/b/52819. For now, try to
675 // narrow it down with a more specific crash.
681 // If the item we're going to is a clone of the item we're at, then we do
682 // not need to load it again. The current frame tree and the frame tree
683 // snapshot in the item have to match.
684 // Note: Some clients treat a navigation to the current history item as
685 // a reload. Thus, if item1 and item2 are the same, we need to create a
686 // new document and should not consider them clones.
687 // (See http://webkit.org/b/35532 for details.)
688 return item1 != item2
689 && item1->itemSequenceNumber() == item2->itemSequenceNumber()
690 && currentFramesMatchItem(item1)
691 && item2->hasSameFrames(item1);
694 // Helper method that determines whether the current frame tree matches given history item's.
695 bool HistoryController::currentFramesMatchItem(HistoryItem* item) const
697 if ((!m_frame->tree()->uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame->tree()->uniqueName() != item->target())
700 const HistoryItemVector& childItems = item->children();
701 if (childItems.size() != m_frame->tree()->childCount())
704 unsigned size = childItems.size();
705 for (unsigned i = 0; i < size; ++i) {
706 if (!m_frame->tree()->child(childItems[i]->target()))
713 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
715 // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.
716 // The item that was the target of the user's navigation is designated as the "targetItem".
717 // When this function is called with doClip=true we're able to create the whole tree except for the target's children,
718 // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
720 Page* page = m_frame->page();
724 if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
727 Frame* mainFrame = page->mainFrame();
729 FrameLoader* frameLoader = mainFrame->loader();
731 frameLoader->checkDidPerformFirstNavigation();
733 RefPtr<HistoryItem> topItem = frameLoader->history()->createItemTree(m_frame, doClip);
734 LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
735 page->backForward()->addItem(topItem.release());
738 void HistoryController::updateCurrentItem()
743 DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
745 if (!documentLoader->unreachableURL().isEmpty())
748 if (m_currentItem->url() != documentLoader->url()) {
749 // We ended up on a completely different URL this time, so the HistoryItem
750 // needs to be re-initialized. Preserve the isTargetItem flag as it is a
751 // property of how this HistoryItem was originally created and is not
752 // dependent on the document.
753 bool isTargetItem = m_currentItem->isTargetItem();
754 m_currentItem->reset();
755 initializeItem(m_currentItem.get());
756 m_currentItem->setIsTargetItem(isTargetItem);
758 // Even if the final URL didn't change, the form data may have changed.
759 m_currentItem->setFormInfoFromRequest(documentLoader->request());
763 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
768 Page* page = m_frame->page();
771 // Get a HistoryItem tree for the current frame tree.
772 RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false);
774 // Override data in the current item (created by createItemTree) to reflect
775 // the pushState() arguments.
776 m_currentItem->setTitle(title);
777 m_currentItem->setStateObject(stateObject);
778 m_currentItem->setURLString(urlString);
780 page->backForward()->addItem(topItem.release());
783 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
788 if (!urlString.isEmpty())
789 m_currentItem->setURLString(urlString);
790 m_currentItem->setTitle(title);
791 m_currentItem->setStateObject(stateObject);
794 } // namespace WebCore