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 "BackForwardList.h" 35 #include "CachedPage.h" 36 #include "CString.h" 37 #include "DocumentLoader.h" 38 #include "Frame.h" 39 #include "FrameLoader.h" 40 #include "FrameLoaderClient.h" 41 #include "FrameTree.h" 42 #include "FrameView.h" 43 #include "HistoryItem.h" 44 #include "Logging.h" 45 #include "Page.h" 46 #include "PageCache.h" 47 #include "PageGroup.h" 48 #include "Settings.h" 49 50 namespace WebCore { 51 52 HistoryController::HistoryController(Frame* frame) 53 : m_frame(frame) 54 { 55 } 56 57 HistoryController::~HistoryController() 58 { 59 } 60 61 void HistoryController::saveScrollPositionAndViewStateToItem(HistoryItem* item) 62 { 63 if (!item || !m_frame->view()) 64 return; 65 66 item->setScrollPoint(m_frame->view()->scrollPosition()); 67 // FIXME: It would be great to work out a way to put this code in WebCore instead of calling through to the client. 68 m_frame->loader()->client()->saveViewStateToItem(item); 69 } 70 71 /* 72 There is a race condition between the layout and load completion that affects restoring the scroll position. 73 We try to restore the scroll position at both the first layout and upon load completion. 74 75 1) If first layout happens before the load completes, we want to restore the scroll position then so that the 76 first time we draw the page is already scrolled to the right place, instead of starting at the top and later 77 jumping down. It is possible that the old scroll position is past the part of the doc laid out so far, in 78 which case the restore silent fails and we will fix it in when we try to restore on doc completion. 79 2) If the layout happens after the load completes, the attempt to restore at load completion time silently 80 fails. We then successfully restore it when the layout happens. 81 */ 82 void HistoryController::restoreScrollPositionAndViewState() 83 { 84 if (!m_frame->loader()->committedFirstRealDocumentLoad()) 85 return; 86 87 ASSERT(m_currentItem); 88 89 // FIXME: As the ASSERT attests, it seems we should always have a currentItem here. 90 // One counterexample is <rdar://problem/4917290> 91 // For now, to cover this issue in release builds, there is no technical harm to returning 92 // early and from a user standpoint - as in the above radar - the previous page load failed 93 // so there *is* no scroll or view state to restore! 94 if (!m_currentItem) 95 return; 96 97 // FIXME: It would be great to work out a way to put this code in WebCore instead of calling 98 // through to the client. It's currently used only for the PDF view on Mac. 99 m_frame->loader()->client()->restoreViewState(); 100 101 if (FrameView* view = m_frame->view()) 102 if (!view->wasScrolledByUser()) 103 view->setScrollPosition(m_currentItem->scrollPoint()); 104 } 105 106 void HistoryController::updateBackForwardListForFragmentScroll() 107 { 108 updateBackForwardListClippedAtTarget(false); 109 110 // Since the document isn't changed as a result of a fragment scroll, we should 111 // preserve the DocumentSequenceNumber of the previous item. 112 m_currentItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber()); 113 } 114 115 void HistoryController::saveDocumentState() 116 { 117 // FIXME: Reading this bit of FrameLoader state here is unfortunate. I need to study 118 // this more to see if we can remove this dependency. 119 if (m_frame->loader()->creatingInitialEmptyDocument()) 120 return; 121 122 // For a standard page load, we will have a previous item set, which will be used to 123 // store the form state. However, in some cases we will have no previous item, and 124 // the current item is the right place to save the state. One example is when we 125 // detach a bunch of frames because we are navigating from a site with frames to 126 // another site. Another is when saving the frame state of a frame that is not the 127 // target of the current navigation (if we even decide to save with that granularity). 128 129 // Because of previousItem's "masking" of currentItem for this purpose, it's important 130 // that previousItem be cleared at the end of a page transition. We leverage the 131 // checkLoadComplete recursion to achieve this goal. 132 133 HistoryItem* item = m_previousItem ? m_previousItem.get() : m_currentItem.get(); 134 if (!item) 135 return; 136 137 Document* document = m_frame->document(); 138 ASSERT(document); 139 140 if (item->isCurrentDocument(document)) { 141 LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame->tree()->name().string().utf8().data(), item); 142 item->setDocumentState(document->formElementsState()); 143 } 144 } 145 146 // Walk the frame tree, telling all frames to save their form state into their current 147 // history item. 148 void HistoryController::saveDocumentAndScrollState() 149 { 150 for (Frame* frame = m_frame; frame; frame = frame->tree()->traverseNext(m_frame)) { 151 frame->loader()->history()->saveDocumentState(); 152 frame->loader()->history()->saveScrollPositionAndViewStateToItem(frame->loader()->history()->currentItem()); 153 } 154 } 155 156 void HistoryController::restoreDocumentState() 157 { 158 Document* doc = m_frame->document(); 159 160 HistoryItem* itemToRestore = 0; 161 162 switch (m_frame->loader()->loadType()) { 163 case FrameLoadTypeReload: 164 case FrameLoadTypeReloadFromOrigin: 165 case FrameLoadTypeSame: 166 case FrameLoadTypeReplace: 167 break; 168 case FrameLoadTypeBack: 169 case FrameLoadTypeBackWMLDeckNotAccessible: 170 case FrameLoadTypeForward: 171 case FrameLoadTypeIndexedBackForward: 172 case FrameLoadTypeRedirectWithLockedBackForwardList: 173 case FrameLoadTypeStandard: 174 itemToRestore = m_currentItem.get(); 175 } 176 177 if (!itemToRestore) 178 return; 179 180 LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame->tree()->name().string().utf8().data(), itemToRestore); 181 doc->setStateForNewFormElements(itemToRestore->documentState()); 182 } 183 184 void HistoryController::invalidateCurrentItemCachedPage() 185 { 186 // When we are pre-commit, the currentItem is where the pageCache data resides 187 CachedPage* cachedPage = pageCache()->get(currentItem()); 188 189 // FIXME: This is a grotesque hack to fix <rdar://problem/4059059> Crash in RenderFlow::detach 190 // Somehow the PageState object is not properly updated, and is holding onto a stale document. 191 // Both Xcode and FileMaker see this crash, Safari does not. 192 193 ASSERT(!cachedPage || cachedPage->document() == m_frame->document()); 194 if (cachedPage && cachedPage->document() == m_frame->document()) { 195 cachedPage->document()->setInPageCache(false); 196 cachedPage->clear(); 197 } 198 199 if (cachedPage) 200 pageCache()->remove(currentItem()); 201 } 202 203 // Main funnel for navigating to a previous location (back/forward, non-search snap-back) 204 // This includes recursion to handle loading into framesets properly 205 void HistoryController::goToItem(HistoryItem* targetItem, FrameLoadType type) 206 { 207 ASSERT(!m_frame->tree()->parent()); 208 209 // shouldGoToHistoryItem is a private delegate method. This is needed to fix: 210 // <rdar://problem/3951283> can view pages from the back/forward cache that should be disallowed by Parental Controls 211 // Ultimately, history item navigations should go through the policy delegate. That's covered in: 212 // <rdar://problem/3979539> back/forward cache navigations should consult policy delegate 213 Page* page = m_frame->page(); 214 if (!page) 215 return; 216 if (!m_frame->loader()->client()->shouldGoToHistoryItem(targetItem)) 217 return; 218 219 // Set the BF cursor before commit, which lets the user quickly click back/forward again. 220 // - plus, it only makes sense for the top level of the operation through the frametree, 221 // as opposed to happening for some/one of the page commits that might happen soon 222 BackForwardList* bfList = page->backForwardList(); 223 HistoryItem* currentItem = bfList->currentItem(); 224 bfList->goToItem(targetItem); 225 Settings* settings = m_frame->settings(); 226 page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : targetItem); 227 recursiveGoToItem(targetItem, currentItem, type); 228 } 229 230 // Walk the frame tree and ensure that the URLs match the URLs in the item. 231 bool HistoryController::urlsMatchItem(HistoryItem* item) const 232 { 233 const KURL& currentURL = m_frame->loader()->documentLoader()->url(); 234 if (!equalIgnoringFragmentIdentifier(currentURL, item->url())) 235 return false; 236 237 const HistoryItemVector& childItems = item->children(); 238 239 unsigned size = childItems.size(); 240 for (unsigned i = 0; i < size; ++i) { 241 Frame* childFrame = m_frame->tree()->child(childItems[i]->target()); 242 if (childFrame && !childFrame->loader()->history()->urlsMatchItem(childItems[i].get())) 243 return false; 244 } 245 246 return true; 247 } 248 249 void HistoryController::updateForBackForwardNavigation() 250 { 251 #if !LOG_DISABLED 252 if (m_frame->loader()->documentLoader()) 253 LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().utf8().data()); 254 #endif 255 256 // Must grab the current scroll position before disturbing it 257 saveScrollPositionAndViewStateToItem(m_previousItem.get()); 258 } 259 260 void HistoryController::updateForReload() 261 { 262 #if !LOG_DISABLED 263 if (m_frame->loader()->documentLoader()) 264 LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame->loader()->documentLoader()->title().utf8().data()); 265 #endif 266 267 if (m_currentItem) { 268 pageCache()->remove(m_currentItem.get()); 269 270 if (m_frame->loader()->loadType() == FrameLoadTypeReload || m_frame->loader()->loadType() == FrameLoadTypeReloadFromOrigin) 271 saveScrollPositionAndViewStateToItem(m_currentItem.get()); 272 273 // Sometimes loading a page again leads to a different result because of cookies. Bugzilla 4072 274 if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty()) 275 m_currentItem->setURL(m_frame->loader()->documentLoader()->requestURL()); 276 } 277 } 278 279 // There are 3 things you might think of as "history", all of which are handled by these functions. 280 // 281 // 1) Back/forward: The m_currentItem is part of this mechanism. 282 // 2) Global history: Handled by the client. 283 // 3) Visited links: Handled by the PageGroup. 284 285 void HistoryController::updateForStandardLoad() 286 { 287 LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data()); 288 289 FrameLoader* frameLoader = m_frame->loader(); 290 291 Settings* settings = m_frame->settings(); 292 bool needPrivacy = !settings || settings->privateBrowsingEnabled(); 293 const KURL& historyURL = frameLoader->documentLoader()->urlForHistory(); 294 295 if (!frameLoader->documentLoader()->isClientRedirect()) { 296 if (!historyURL.isEmpty()) { 297 updateBackForwardListClippedAtTarget(true); 298 if (!needPrivacy) { 299 frameLoader->client()->updateGlobalHistory(); 300 frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true); 301 if (frameLoader->documentLoader()->unreachableURL().isEmpty()) 302 frameLoader->client()->updateGlobalHistoryRedirectLinks(); 303 } 304 if (Page* page = m_frame->page()) 305 page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForwardList()->currentItem()); 306 } 307 } else if (frameLoader->documentLoader()->unreachableURL().isEmpty() && m_currentItem) { 308 m_currentItem->setURL(frameLoader->documentLoader()->url()); 309 m_currentItem->setFormInfoFromRequest(frameLoader->documentLoader()->request()); 310 } 311 312 if (!historyURL.isEmpty() && !needPrivacy) { 313 if (Page* page = m_frame->page()) 314 page->group().addVisitedLink(historyURL); 315 316 if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !frameLoader->url().isEmpty()) 317 frameLoader->client()->updateGlobalHistoryRedirectLinks(); 318 } 319 } 320 321 void HistoryController::updateForRedirectWithLockedBackForwardList() 322 { 323 #if !LOG_DISABLED 324 if (m_frame->loader()->documentLoader()) 325 LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().utf8().data()); 326 #endif 327 328 Settings* settings = m_frame->settings(); 329 bool needPrivacy = !settings || settings->privateBrowsingEnabled(); 330 const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory(); 331 332 if (m_frame->loader()->documentLoader()->isClientRedirect()) { 333 if (!m_currentItem && !m_frame->tree()->parent()) { 334 if (!historyURL.isEmpty()) { 335 updateBackForwardListClippedAtTarget(true); 336 if (!needPrivacy) { 337 m_frame->loader()->client()->updateGlobalHistory(); 338 m_frame->loader()->documentLoader()->setDidCreateGlobalHistoryEntry(true); 339 if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty()) 340 m_frame->loader()->client()->updateGlobalHistoryRedirectLinks(); 341 } 342 if (Page* page = m_frame->page()) 343 page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForwardList()->currentItem()); 344 } 345 } 346 if (m_currentItem) { 347 m_currentItem->setURL(m_frame->loader()->documentLoader()->url()); 348 m_currentItem->setFormInfoFromRequest(m_frame->loader()->documentLoader()->request()); 349 } 350 } else { 351 Frame* parentFrame = m_frame->tree()->parent(); 352 if (parentFrame && parentFrame->loader()->history()->m_currentItem) 353 parentFrame->loader()->history()->m_currentItem->setChildItem(createItem(true)); 354 } 355 356 if (!historyURL.isEmpty() && !needPrivacy) { 357 if (Page* page = m_frame->page()) 358 page->group().addVisitedLink(historyURL); 359 360 if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->loader()->url().isEmpty()) 361 m_frame->loader()->client()->updateGlobalHistoryRedirectLinks(); 362 } 363 } 364 365 void HistoryController::updateForClientRedirect() 366 { 367 #if !LOG_DISABLED 368 if (m_frame->loader()->documentLoader()) 369 LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame->loader()->documentLoader()->title().utf8().data()); 370 #endif 371 372 // Clear out form data so we don't try to restore it into the incoming page. Must happen after 373 // webcore has closed the URL and saved away the form state. 374 if (m_currentItem) { 375 m_currentItem->clearDocumentState(); 376 m_currentItem->clearScrollPoint(); 377 } 378 379 Settings* settings = m_frame->settings(); 380 bool needPrivacy = !settings || settings->privateBrowsingEnabled(); 381 const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory(); 382 383 if (!historyURL.isEmpty() && !needPrivacy) { 384 if (Page* page = m_frame->page()) 385 page->group().addVisitedLink(historyURL); 386 } 387 } 388 389 void HistoryController::updateForCommit() 390 { 391 FrameLoader* frameLoader = m_frame->loader(); 392 #if !LOG_DISABLED 393 if (frameLoader->documentLoader()) 394 LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().utf8().data()); 395 #endif 396 FrameLoadType type = frameLoader->loadType(); 397 if (isBackForwardLoadType(type) || 398 ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->provisionalDocumentLoader()->unreachableURL().isEmpty())) { 399 // Once committed, we want to use current item for saving DocState, and 400 // the provisional item for restoring state. 401 // Note previousItem must be set before we close the URL, which will 402 // happen when the data source is made non-provisional below 403 m_previousItem = m_currentItem; 404 ASSERT(m_provisionalItem); 405 m_currentItem = m_provisionalItem; 406 m_provisionalItem = 0; 407 } 408 } 409 410 void HistoryController::updateForSameDocumentNavigation() 411 { 412 if (m_frame->loader()->url().isEmpty()) 413 return; 414 415 Settings* settings = m_frame->settings(); 416 if (!settings || settings->privateBrowsingEnabled()) 417 return; 418 419 Page* page = m_frame->page(); 420 if (!page) 421 return; 422 423 page->group().addVisitedLink(m_frame->loader()->url()); 424 } 425 426 void HistoryController::updateForFrameLoadCompleted() 427 { 428 // Even if already complete, we might have set a previous item on a frame that 429 // didn't do any data loading on the past transaction. Make sure to clear these out. 430 m_previousItem = 0; 431 } 432 433 void HistoryController::setCurrentItem(HistoryItem* item) 434 { 435 m_currentItem = item; 436 } 437 438 void HistoryController::setCurrentItemTitle(const String& title) 439 { 440 if (m_currentItem) 441 m_currentItem->setTitle(title); 442 } 443 444 void HistoryController::setProvisionalItem(HistoryItem* item) 445 { 446 m_provisionalItem = item; 447 } 448 449 PassRefPtr<HistoryItem> HistoryController::createItem(bool useOriginal) 450 { 451 DocumentLoader* docLoader = m_frame->loader()->documentLoader(); 452 453 KURL unreachableURL = docLoader ? docLoader->unreachableURL() : KURL(); 454 455 KURL url; 456 KURL originalURL; 457 458 if (!unreachableURL.isEmpty()) { 459 url = unreachableURL; 460 originalURL = unreachableURL; 461 } else { 462 originalURL = docLoader ? docLoader->originalURL() : KURL(); 463 if (useOriginal) 464 url = originalURL; 465 else if (docLoader) 466 url = docLoader->requestURL(); 467 } 468 469 LOG(History, "WebCoreHistory: Creating item for %s", url.string().ascii().data()); 470 471 // Frames that have never successfully loaded any content 472 // may have no URL at all. Currently our history code can't 473 // deal with such things, so we nip that in the bud here. 474 // Later we may want to learn to live with nil for URL. 475 // See bug 3368236 and related bugs for more information. 476 if (url.isEmpty()) 477 url = blankURL(); 478 if (originalURL.isEmpty()) 479 originalURL = blankURL(); 480 481 Frame* parentFrame = m_frame->tree()->parent(); 482 String parent = parentFrame ? parentFrame->tree()->name() : ""; 483 String title = docLoader ? docLoader->title() : ""; 484 485 RefPtr<HistoryItem> item = HistoryItem::create(url, m_frame->tree()->name(), parent, title); 486 item->setOriginalURLString(originalURL.string()); 487 488 if (!unreachableURL.isEmpty() || !docLoader || docLoader->response().httpStatusCode() >= 400) 489 item->setLastVisitWasFailure(true); 490 491 // Save form state if this is a POST 492 if (docLoader) { 493 if (useOriginal) 494 item->setFormInfoFromRequest(docLoader->originalRequest()); 495 else 496 item->setFormInfoFromRequest(docLoader->request()); 497 } 498 499 // Set the item for which we will save document state 500 m_previousItem = m_currentItem; 501 m_currentItem = item; 502 503 return item.release(); 504 } 505 506 PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget) 507 { 508 RefPtr<HistoryItem> bfItem = createItem(m_frame->tree()->parent() ? true : false); 509 if (m_previousItem) 510 saveScrollPositionAndViewStateToItem(m_previousItem.get()); 511 if (!(clipAtTarget && m_frame == targetFrame)) { 512 // save frame state for items that aren't loading (khtml doesn't save those) 513 saveDocumentState(); 514 for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) { 515 FrameLoader* childLoader = child->loader(); 516 bool hasChildLoaded = childLoader->frameHasLoaded(); 517 518 // If the child is a frame corresponding to an <object> element that never loaded, 519 // we don't want to create a history item, because that causes fallback content 520 // to be ignored on reload. 521 522 if (!(!hasChildLoaded && childLoader->isHostedByObjectElement())) 523 bfItem->addChildItem(childLoader->history()->createItemTree(targetFrame, clipAtTarget)); 524 } 525 } 526 if (m_frame == targetFrame) 527 bfItem->setIsTargetItem(true); 528 return bfItem; 529 } 530 531 // The general idea here is to traverse the frame tree and the item tree in parallel, 532 // tracking whether each frame already has the content the item requests. If there is 533 // a match (by URL), we just restore scroll position and recurse. Otherwise we must 534 // reload that frame, and all its kids. 535 void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type) 536 { 537 ASSERT(item); 538 ASSERT(fromItem); 539 540 KURL itemURL = item->url(); 541 KURL currentURL; 542 if (m_frame->loader()->documentLoader()) 543 currentURL = m_frame->loader()->documentLoader()->url(); 544 545 // Always reload the target frame of the item we're going to. This ensures that we will 546 // do -some- load for the transition, which means a proper notification will be posted 547 // to the app. 548 // The exact URL has to match, including fragment. We want to go through the _load 549 // method, even if to do a within-page navigation. 550 // The current frame tree and the frame tree snapshot in the item have to match. 551 if (!item->isTargetItem() && 552 itemURL == currentURL && 553 ((m_frame->tree()->name().isEmpty() && item->target().isEmpty()) || m_frame->tree()->name() == item->target()) && 554 childFramesMatchItem(item)) 555 { 556 // This content is good, so leave it alone and look for children that need reloading 557 // Save form state (works from currentItem, since prevItem is nil) 558 ASSERT(!m_previousItem); 559 saveDocumentState(); 560 saveScrollPositionAndViewStateToItem(m_currentItem.get()); 561 562 if (FrameView* view = m_frame->view()) 563 view->setWasScrolledByUser(false); 564 565 m_currentItem = item; 566 567 // Restore form state (works from currentItem) 568 restoreDocumentState(); 569 570 // Restore the scroll position (we choose to do this rather than going back to the anchor point) 571 restoreScrollPositionAndViewState(); 572 573 const HistoryItemVector& childItems = item->children(); 574 575 int size = childItems.size(); 576 for (int i = 0; i < size; ++i) { 577 String childFrameName = childItems[i]->target(); 578 HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName); 579 ASSERT(fromChildItem || fromItem->isTargetItem()); 580 Frame* childFrame = m_frame->tree()->child(childFrameName); 581 ASSERT(childFrame); 582 childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type); 583 } 584 } else { 585 m_frame->loader()->loadItem(item, type); 586 } 587 } 588 589 // helper method that determines whether the subframes described by the item's subitems 590 // match our own current frameset 591 bool HistoryController::childFramesMatchItem(HistoryItem* item) const 592 { 593 const HistoryItemVector& childItems = item->children(); 594 if (childItems.size() != m_frame->tree()->childCount()) 595 return false; 596 597 unsigned size = childItems.size(); 598 for (unsigned i = 0; i < size; ++i) { 599 if (!m_frame->tree()->child(childItems[i]->target())) 600 return false; 601 } 602 603 // Found matches for all item targets 604 return true; 605 } 606 607 void HistoryController::updateBackForwardListClippedAtTarget(bool doClip) 608 { 609 // In the case of saving state about a page with frames, we store a tree of items that mirrors the frame tree. 610 // The item that was the target of the user's navigation is designated as the "targetItem". 611 // When this function is called with doClip=true we're able to create the whole tree except for the target's children, 612 // which will be loaded in the future. That part of the tree will be filled out as the child loads are committed. 613 614 Page* page = m_frame->page(); 615 if (!page) 616 return; 617 618 if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty()) 619 return; 620 621 Frame* mainFrame = page->mainFrame(); 622 ASSERT(mainFrame); 623 FrameLoader* frameLoader = mainFrame->loader(); 624 625 frameLoader->checkDidPerformFirstNavigation(); 626 627 RefPtr<HistoryItem> item = frameLoader->history()->createItemTree(m_frame, doClip); 628 LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", item.get(), m_frame->loader()->documentLoader()->url().string().ascii().data()); 629 page->backForwardList()->addItem(item); 630 } 631 632 void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString) 633 { 634 Page* page = m_frame->page(); 635 ASSERT(page); 636 637 // Get a HistoryItem tree for the current frame tree. 638 RefPtr<HistoryItem> item = createItemTree(m_frame, false); 639 ASSERT(item->isTargetItem()); 640 641 // Override data in the target item to reflect the pushState() arguments. 642 item->setTitle(title); 643 item->setStateObject(stateObject); 644 item->setURLString(urlString); 645 646 // Since the document isn't changed as a result of a pushState call, we 647 // should preserve the DocumentSequenceNumber of the previous item. 648 item->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber()); 649 650 page->backForwardList()->pushStateItem(item.release()); 651 } 652 653 void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString) 654 { 655 Page* page = m_frame->page(); 656 ASSERT(page); 657 HistoryItem* current = page->backForwardList()->currentItem(); 658 ASSERT(current); 659 660 if (!urlString.isEmpty()) 661 current->setURLString(urlString); 662 current->setTitle(title); 663 current->setStateObject(stateObject); 664 } 665 666 } // namespace WebCore 667