Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 11 | * notice, this list of conditions and the following disclaimer. |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 12 | * 2. Redistributions in binary form must reproduce the above copyright |
| 13 | * notice, this list of conditions and the following disclaimer in the |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 14 | * documentation and/or other materials provided with the distribution. |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 17 | * from this software without specific prior written permission. |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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" |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 45 | #include "core/page/scrolling/ScrollingCoordinator.h" |
| 46 | #include "core/platform/Logging.h" |
Ben Murdoch | 591b958 | 2013-07-10 11:41:44 +0100 | [diff] [blame] | 47 | #include "wtf/text/CString.h" |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 48 | |
| 49 | namespace WebCore { |
| 50 | |
| 51 | HistoryController::HistoryController(Frame* frame) |
| 52 | : m_frame(frame) |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 85 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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); |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 99 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 103 | // early and from a user standpoint - as in the above radar - the previous page load failed |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |
Torne (Richard Coles) | 5267f70 | 2013-06-11 10:57:24 +0100 | [diff] [blame] | 119 | view->setScrollPositionNonProgrammatically(m_currentItem->scrollPoint()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | void HistoryController::updateBackForwardListForFragmentScroll() |
| 125 | { |
| 126 | updateBackForwardListClippedAtTarget(false); |
| 127 | } |
| 128 | |
| 129 | void HistoryController::saveDocumentState() |
| 130 | { |
Torne (Richard Coles) | f5e4ad5 | 2013-08-05 13:57:57 +0100 | [diff] [blame] | 131 | if (!m_currentItem) |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 132 | return; |
| 133 | |
| 134 | Document* document = m_frame->document(); |
| 135 | ASSERT(document); |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 136 | |
Torne (Richard Coles) | f5e4ad5 | 2013-08-05 13:57:57 +0100 | [diff] [blame] | 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()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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(); |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 172 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 173 | HistoryItem* itemToRestore = 0; |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 174 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 175 | switch (m_frame->loader()->loadType()) { |
| 176 | case FrameLoadTypeReload: |
| 177 | case FrameLoadTypeReloadFromOrigin: |
| 178 | case FrameLoadTypeSame: |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 179 | break; |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 180 | case FrameLoadTypeBackForward: |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 181 | case FrameLoadTypeRedirectWithLockedBackForwardList: |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 182 | case FrameLoadTypeInitialInChildFrame: |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 183 | case FrameLoadTypeStandard: |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 184 | itemToRestore = m_currentItem.get(); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 185 | } |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 186 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 209 | void HistoryController::goToItem(HistoryItem* targetItem) |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 210 | { |
| 211 | ASSERT(!m_frame->tree()->parent()); |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 212 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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; |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 237 | recursiveSetProvisionalItem(targetItem, currentItem.get()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 238 | // Now that all other frames have provisional items, do the actual navigation. |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 239 | recursiveGoToItem(targetItem, currentItem.get()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 240 | } |
| 241 | |
| 242 | void HistoryController::setDefersLoading(bool defer) |
| 243 | { |
| 244 | m_defersLoading = defer; |
| 245 | if (!defer && m_deferredItem) { |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 246 | goToItem(m_deferredItem.get()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 | |
Torne (Richard Coles) | f5e4ad5 | 2013-08-05 13:57:57 +0100 | [diff] [blame] | 258 | saveScrollPositionAndViewStateToItem(m_previousItem.get()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 | // |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 287 | void HistoryController::updateForStandardLoad() |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 288 | { |
| 289 | LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data()); |
| 290 | |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 291 | if (!m_frame->loader()->documentLoader()->urlForHistory().isEmpty()) |
| 292 | updateBackForwardListClippedAtTarget(true); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 293 | } |
| 294 | |
| 295 | void HistoryController::updateForRedirectWithLockedBackForwardList() |
| 296 | { |
| 297 | #if !LOG_DISABLED |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 298 | LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 299 | #endif |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 300 | |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 301 | if (!m_currentItem && !m_frame->tree()->parent()) { |
| 302 | if (!m_frame->loader()->documentLoader()->urlForHistory().isEmpty()) |
| 303 | updateBackForwardListClippedAtTarget(true); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 304 | } |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 305 | // The client redirect replaces the current history item. |
| 306 | updateCurrentItem(); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 307 | } |
| 308 | |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 309 | void HistoryController::updateForInitialLoadInChildFrame() |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 310 | { |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 311 | Frame* parentFrame = m_frame->tree()->parent(); |
| 312 | if (parentFrame && parentFrame->loader()->history()->m_currentItem) |
| 313 | parentFrame->loader()->history()->m_currentItem->setChildItem(createItem()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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(); |
Ben Murdoch | fff8884 | 2013-07-30 15:20:09 +0100 | [diff] [blame] | 324 | if (isBackForwardLoadType(type) || (isReloadTypeWithProvisionalItem(type) && !frameLoader->documentLoader()->unreachableURL().isEmpty())) { |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 | } |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 341 | |
| 342 | switch (type) { |
| 343 | case FrameLoadTypeBackForward: |
| 344 | updateForBackForwardNavigation(); |
| 345 | return; |
| 346 | case FrameLoadTypeReload: |
| 347 | case FrameLoadTypeReloadFromOrigin: |
| 348 | case FrameLoadTypeSame: |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 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 | } |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 363 | } |
| 364 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Torne (Richard Coles) | f5e4ad5 | 2013-08-05 13:57:57 +0100 | [diff] [blame] | 379 | // Save form state |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 380 | if (m_currentItem && itemsAreClones(m_currentItem.get(), m_provisionalItem.get())) { |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 441 | void HistoryController::setCurrentItem(HistoryItem* item) |
| 442 | { |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 491 | if (url.isEmpty()) |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 492 | url = blankURL(); |
| 493 | if (originalURL.isEmpty()) |
| 494 | originalURL = blankURL(); |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 495 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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()); |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 515 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 516 | // Set the item for which we will save document state |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 517 | m_previousItem = m_currentItem; |
| 518 | m_currentItem = item; |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 519 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 520 | return item.release(); |
| 521 | } |
| 522 | |
| 523 | PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget) |
| 524 | { |
| 525 | RefPtr<HistoryItem> bfItem = createItem(); |
Torne (Richard Coles) | f5e4ad5 | 2013-08-05 13:57:57 +0100 | [diff] [blame] | 526 | saveScrollPositionAndViewStateToItem(m_previousItem.get()); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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()) { |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 546 | FrameLoader* childLoader = child->loader(); |
| 547 | if (childLoader->stateMachine()->startedFirstRealLoad() || !childLoader->isHostedByObjectElement()) |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 561 | void HistoryController::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem) |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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); |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 579 | childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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. |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 586 | void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem) |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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); |
Torne (Richard Coles) | 81a5157 | 2013-05-13 16:52:28 +0100 | [diff] [blame] | 601 | childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 602 | } |
| 603 | } else { |
Ben Murdoch | 7757ec2 | 2013-07-23 11:17:36 +0100 | [diff] [blame] | 604 | m_frame->loader()->loadHistoryItem(item); |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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; |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 630 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 631 | const HistoryItemVector& childItems = item->children(); |
| 632 | if (childItems.size() != m_frame->tree()->childCount()) |
| 633 | return false; |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 634 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 | } |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 640 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 641 | return true; |
| 642 | } |
| 643 | |
| 644 | void HistoryController::updateBackForwardListClippedAtTarget(bool doClip) |
| 645 | { |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 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, |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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); |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 701 | |
Torne (Richard Coles) | 53e740f | 2013-05-09 18:38:43 +0100 | [diff] [blame] | 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 |