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 RefPtr<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.get(), type);
249 // Now that all other frames have provisional items, do the actual navigation.
250 recursiveGoToItem(targetItem, currentItem.get(), 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 || isReplaceLoadTypeWithProvisionalItem(type)
407 || ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) {
408 // Once committed, we want to use current item for saving DocState, and
409 // the provisional item for restoring state.
410 // Note previousItem must be set before we close the URL, which will
411 // happen when the data source is made non-provisional below
412 m_frameLoadComplete = false;
413 m_previousItem = m_currentItem;
414 ASSERT(m_provisionalItem);
415 m_currentItem = m_provisionalItem;
416 m_provisionalItem = 0;
418 // Tell all other frames in the tree to commit their provisional items and
419 // restore their scroll position. We'll avoid this frame (which has already
420 // committed) and its children (which will be replaced).
421 Page* page = m_frame->page();
423 page->mainFrame()->loader()->history()->recursiveUpdateForCommit();
427 bool HistoryController::isReplaceLoadTypeWithProvisionalItem(FrameLoadType type)
429 // Going back to an error page in a subframe can trigger a FrameLoadTypeReplace
430 // while m_provisionalItem is set, so we need to commit it.
431 return type == FrameLoadTypeReplace && m_provisionalItem;
434 void HistoryController::recursiveUpdateForCommit()
436 // The frame that navigated will now have a null provisional item.
437 // Ignore it and its children.
438 if (!m_provisionalItem)
441 // For each frame that already had the content the item requested (based on
442 // (a matching URL and frame tree snapshot), just restore the scroll position.
443 // Save form state (works from currentItem, since m_frameLoadComplete is true)
444 ASSERT(m_frameLoadComplete);
446 saveScrollPositionAndViewStateToItem(m_currentItem.get());
448 if (FrameView* view = m_frame->view())
449 view->setWasScrolledByUser(false);
451 // Now commit the provisional item
452 m_frameLoadComplete = false;
453 m_previousItem = m_currentItem;
454 m_currentItem = m_provisionalItem;
455 m_provisionalItem = 0;
457 // Restore form state (works from currentItem)
458 restoreDocumentState();
460 // Restore the scroll position (we choose to do this rather than going back to the anchor point)
461 restoreScrollPositionAndViewState();
463 // Iterate over the rest of the tree
464 for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
465 child->loader()->history()->recursiveUpdateForCommit();
468 void HistoryController::updateForSameDocumentNavigation()
470 if (m_frame->document()->url().isEmpty())
473 Settings* settings = m_frame->settings();
474 if (!settings || settings->privateBrowsingEnabled())
477 Page* page = m_frame->page();
481 addVisitedLink(page, m_frame->document()->url());
482 page->mainFrame()->loader()->history()->recursiveUpdateForSameDocumentNavigation();
485 void HistoryController::recursiveUpdateForSameDocumentNavigation()
487 // The frame that navigated will now have a null provisional item.
488 // Ignore it and its children.
489 if (!m_provisionalItem)
492 // Commit the provisional item.
493 m_frameLoadComplete = false;
494 m_previousItem = m_currentItem;
495 m_currentItem = m_provisionalItem;
496 m_provisionalItem = 0;
498 // Iterate over the rest of the tree.
499 for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
500 child->loader()->history()->recursiveUpdateForSameDocumentNavigation();
503 void HistoryController::updateForFrameLoadCompleted()
505 // Even if already complete, we might have set a previous item on a frame that
506 // didn't do any data loading on the past transaction. Make sure to track that
507 // the load is complete so that we use the current item instead.
508 m_frameLoadComplete = true;
511 void HistoryController::setCurrentItem(HistoryItem* item)
513 m_frameLoadComplete = false;
514 m_previousItem = m_currentItem;
515 m_currentItem = item;
518 void HistoryController::setCurrentItemTitle(const String& title)
521 m_currentItem->setTitle(title);
524 bool HistoryController::currentItemShouldBeReplaced() const
526 // From the HTML5 spec for location.assign():
527 // "If the browsing context's session history contains only one Document,
528 // and that was the about:blank Document created when the browsing context
529 // was created, then the navigation must be done with replacement enabled."
530 return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL());
533 void HistoryController::setProvisionalItem(HistoryItem* item)
535 m_provisionalItem = item;
538 void HistoryController::initializeItem(HistoryItem* item)
540 DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
541 ASSERT(documentLoader);
543 KURL unreachableURL = documentLoader->unreachableURL();
548 if (!unreachableURL.isEmpty()) {
549 url = unreachableURL;
550 originalURL = unreachableURL;
552 url = documentLoader->url();
553 originalURL = documentLoader->originalURL();
556 // Frames that have never successfully loaded any content
557 // may have no URL at all. Currently our history code can't
558 // deal with such things, so we nip that in the bud here.
559 // Later we may want to learn to live with nil for URL.
560 // See bug 3368236 and related bugs for more information.
563 if (originalURL.isEmpty())
564 originalURL = blankURL();
566 Frame* parentFrame = m_frame->tree()->parent();
567 String parent = parentFrame ? parentFrame->tree()->uniqueName() : "";
568 String title = documentLoader->title();
571 item->setTarget(m_frame->tree()->uniqueName());
572 item->setParent(parent);
573 item->setTitle(title);
574 item->setOriginalURLString(originalURL.string());
576 if (!unreachableURL.isEmpty() || documentLoader->response().httpStatusCode() >= 400)
577 item->setLastVisitWasFailure(true);
579 // Save form state if this is a POST
580 item->setFormInfoFromRequest(documentLoader->request());
583 PassRefPtr<HistoryItem> HistoryController::createItem()
585 RefPtr<HistoryItem> item = HistoryItem::create();
586 initializeItem(item.get());
588 // Set the item for which we will save document state
589 m_frameLoadComplete = false;
590 m_previousItem = m_currentItem;
591 m_currentItem = item;
593 return item.release();
596 PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
598 RefPtr<HistoryItem> bfItem = createItem();
599 if (!m_frameLoadComplete)
600 saveScrollPositionAndViewStateToItem(m_previousItem.get());
602 if (!clipAtTarget || m_frame != targetFrame) {
603 // save frame state for items that aren't loading (khtml doesn't save those)
606 // clipAtTarget is false for navigations within the same document, so
607 // we should copy the documentSequenceNumber over to the newly create
608 // item. Non-target items are just clones, and they should therefore
609 // preserve the same itemSequenceNumber.
610 if (m_previousItem) {
611 if (m_frame != targetFrame)
612 bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber());
613 bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber());
616 for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
617 FrameLoader* childLoader = child->loader();
618 bool hasChildLoaded = childLoader->frameHasLoaded();
620 // If the child is a frame corresponding to an <object> element that never loaded,
621 // we don't want to create a history item, because that causes fallback content
622 // to be ignored on reload.
624 if (!(!hasChildLoaded && childLoader->isHostedByObjectElement()))
625 bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget));
628 // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber.
629 if (m_frame == targetFrame)
630 bfItem->setIsTargetItem(true);
634 // The general idea here is to traverse the frame tree and the item tree in parallel,
635 // tracking whether each frame already has the content the item requests. If there is
636 // a match, we set the provisional item and recurse. Otherwise we will reload that
637 // frame and all its kids in recursiveGoToItem.
638 void HistoryController::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
643 if (itemsAreClones(item, fromItem)) {
644 // Set provisional item, which will be committed in recursiveUpdateForCommit.
645 m_provisionalItem = item;
647 const HistoryItemVector& childItems = item->children();
649 int size = childItems.size();
651 for (int i = 0; i < size; ++i) {
652 String childFrameName = childItems[i]->target();
653 HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
654 ASSERT(fromChildItem);
655 Frame* childFrame = m_frame->tree()->child(childFrameName);
657 childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem, type);
662 // We now traverse the frame tree and item tree a second time, loading frames that
663 // do have the content the item requests.
664 void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type)
669 if (itemsAreClones(item, fromItem)) {
670 // Just iterate over the rest, looking for frames to navigate.
671 const HistoryItemVector& childItems = item->children();
673 int size = childItems.size();
674 for (int i = 0; i < size; ++i) {
675 String childFrameName = childItems[i]->target();
676 HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName);
677 ASSERT(fromChildItem);
678 Frame* childFrame = m_frame->tree()->child(childFrameName);
680 childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type);
683 m_frame->loader()->loadItem(item, type);
687 bool HistoryController::itemsAreClones(HistoryItem* item1, HistoryItem* item2) const
689 // If the item we're going to is a clone of the item we're at, then we do
690 // not need to load it again. The current frame tree and the frame tree
691 // snapshot in the item have to match.
692 // Note: Some clients treat a navigation to the current history item as
693 // a reload. Thus, if item1 and item2 are the same, we need to create a
694 // new document and should not consider them clones.
695 // (See http://webkit.org/b/35532 for details.)
696 return item1 != item2
697 && item1->itemSequenceNumber() == item2->itemSequenceNumber()
698 && currentFramesMatchItem(item1)
699 && item2->hasSameFrames(item1);
702 // Helper method that determines whether the current frame tree matches given history item's.
703 bool HistoryController::currentFramesMatchItem(HistoryItem* item) const
705 if ((!m_frame->tree()->uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame->tree()->uniqueName() != item->target())
708 const HistoryItemVector& childItems = item->children();
709 if (childItems.size() != m_frame->tree()->childCount())
712 unsigned size = childItems.size();
713 for (unsigned i = 0; i < size; ++i) {
714 if (!m_frame->tree()->child(childItems[i]->target()))
721 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
723 // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree.
724 // The item that was the target of the user's navigation is designated as the "targetItem".
725 // When this function is called with doClip=true we're able to create the whole tree except for the target's children,
726 // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed.
728 Page* page = m_frame->page();
732 if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
735 Frame* mainFrame = page->mainFrame();
737 FrameLoader* frameLoader = mainFrame->loader();
739 frameLoader->checkDidPerformFirstNavigation();
741 RefPtr<HistoryItem> topItem = frameLoader->history()->createItemTree(m_frame, doClip);
742 LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data());
743 page->backForward()->addItem(topItem.release());
746 void HistoryController::updateCurrentItem()
751 DocumentLoader* documentLoader = m_frame->loader()->documentLoader();
753 if (!documentLoader->unreachableURL().isEmpty())
756 if (m_currentItem->url() != documentLoader->url()) {
757 // We ended up on a completely different URL this time, so the HistoryItem
758 // needs to be re-initialized. Preserve the isTargetItem flag as it is a
759 // property of how this HistoryItem was originally created and is not
760 // dependent on the document.
761 bool isTargetItem = m_currentItem->isTargetItem();
762 m_currentItem->reset();
763 initializeItem(m_currentItem.get());
764 m_currentItem->setIsTargetItem(isTargetItem);
766 // Even if the final URL didn't change, the form data may have changed.
767 m_currentItem->setFormInfoFromRequest(documentLoader->request());
771 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
776 Page* page = m_frame->page();
779 // Get a HistoryItem tree for the current frame tree.
780 RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false);
782 // Override data in the current item (created by createItemTree) to reflect
783 // the pushState() arguments.
784 m_currentItem->setTitle(title);
785 m_currentItem->setStateObject(stateObject);
786 m_currentItem->setURLString(urlString);
788 page->backForward()->addItem(topItem.release());
791 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString)
796 if (!urlString.isEmpty())
797 m_currentItem->setURLString(urlString);
798 m_currentItem->setTitle(title);
799 m_currentItem->setStateObject(stateObject);
802 } // namespace WebCore