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