blob: 900765c8a77c06f282461081eeb7e3695eb2180d [file] [log] [blame]
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001/*
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 Murdoch02772c62013-07-26 10:21:05 +010011 * notice, this list of conditions and the following disclaimer.
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010012 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
Ben Murdoch02772c62013-07-26 10:21:05 +010014 * documentation and/or other materials provided with the distribution.
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010015 * 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 Murdoch02772c62013-07-26 10:21:05 +010017 * from this software without specific prior written permission.
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010018 *
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)53e740f2013-05-09 18:38:43 +010045#include "core/page/scrolling/ScrollingCoordinator.h"
46#include "core/platform/Logging.h"
Ben Murdoch591b9582013-07-10 11:41:44 +010047#include "wtf/text/CString.h"
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010048
49namespace WebCore {
50
51HistoryController::HistoryController(Frame* frame)
52 : m_frame(frame)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010053 , m_defersLoading(false)
54{
55}
56
57HistoryController::~HistoryController()
58{
59}
60
61void 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
73void 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 Murdoch02772c62013-07-26 10:21:05 +010085
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010086 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*/
93void HistoryController::restoreScrollPositionAndViewState()
94{
95 if (!m_frame->loader()->stateMachine()->committedFirstRealDocumentLoad())
96 return;
97
98 ASSERT(m_currentItem);
Ben Murdoch02772c62013-07-26 10:21:05 +010099
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100100 // 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 Murdoch02772c62013-07-26 10:21:05 +0100103 // early and from a user standpoint - as in the above radar - the previous page load failed
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100104 // 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)5267f702013-06-11 10:57:24 +0100119 view->setScrollPositionNonProgrammatically(m_currentItem->scrollPoint());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100120 }
121 }
122}
123
124void HistoryController::updateBackForwardListForFragmentScroll()
125{
126 updateBackForwardListClippedAtTarget(false);
127}
128
129void HistoryController::saveDocumentState()
130{
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100131 if (!m_currentItem)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100132 return;
133
134 Document* document = m_frame->document();
135 ASSERT(document);
Ben Murdoch02772c62013-07-26 10:21:05 +0100136
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100137 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)53e740f2013-05-09 18:38:43 +0100140 }
141}
142
143// Walk the frame tree, telling all frames to save their form state into their current
144// history item.
145void 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
153static 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
169void HistoryController::restoreDocumentState()
170{
171 Document* doc = m_frame->document();
Ben Murdoch02772c62013-07-26 10:21:05 +0100172
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100173 HistoryItem* itemToRestore = 0;
Ben Murdoch02772c62013-07-26 10:21:05 +0100174
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100175 switch (m_frame->loader()->loadType()) {
176 case FrameLoadTypeReload:
177 case FrameLoadTypeReloadFromOrigin:
178 case FrameLoadTypeSame:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100179 break;
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100180 case FrameLoadTypeBackForward:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100181 case FrameLoadTypeRedirectWithLockedBackForwardList:
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100182 case FrameLoadTypeInitialInChildFrame:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100183 case FrameLoadTypeStandard:
Ben Murdoch02772c62013-07-26 10:21:05 +0100184 itemToRestore = m_currentItem.get();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100185 }
Ben Murdoch02772c62013-07-26 10:21:05 +0100186
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100187 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
195bool 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)81a51572013-05-13 16:52:28 +0100209void HistoryController::goToItem(HistoryItem* targetItem)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100210{
211 ASSERT(!m_frame->tree()->parent());
Ben Murdoch02772c62013-07-26 10:21:05 +0100212
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100213 // 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)53e740f2013-05-09 18:38:43 +0100224 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)81a51572013-05-13 16:52:28 +0100237 recursiveSetProvisionalItem(targetItem, currentItem.get());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100238 // Now that all other frames have provisional items, do the actual navigation.
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100239 recursiveGoToItem(targetItem, currentItem.get());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100240}
241
242void HistoryController::setDefersLoading(bool defer)
243{
244 m_defersLoading = defer;
245 if (!defer && m_deferredItem) {
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100246 goToItem(m_deferredItem.get());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100247 m_deferredItem = 0;
248 }
249}
250
251void 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)f5e4ad52013-08-05 13:57:57 +0100258 saveScrollPositionAndViewStateToItem(m_previousItem.get());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100259
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
265void 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)81a51572013-05-13 16:52:28 +0100287void HistoryController::updateForStandardLoad()
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100288{
289 LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data());
290
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100291 if (!m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
292 updateBackForwardListClippedAtTarget(true);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100293}
294
295void HistoryController::updateForRedirectWithLockedBackForwardList()
296{
297#if !LOG_DISABLED
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100298 LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().string().utf8().data());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100299#endif
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100300
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100301 if (!m_currentItem && !m_frame->tree()->parent()) {
302 if (!m_frame->loader()->documentLoader()->urlForHistory().isEmpty())
303 updateBackForwardListClippedAtTarget(true);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100304 }
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100305 // The client redirect replaces the current history item.
306 updateCurrentItem();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100307}
308
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100309void HistoryController::updateForInitialLoadInChildFrame()
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100310{
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100311 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)53e740f2013-05-09 18:38:43 +0100314}
315
316void 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 Murdochfff88842013-07-30 15:20:09 +0100324 if (isBackForwardLoadType(type) || (isReloadTypeWithProvisionalItem(type) && !frameLoader->documentLoader()->unreachableURL().isEmpty())) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100325 // 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)53e740f2013-05-09 18:38:43 +0100329 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)81a51572013-05-13 16:52:28 +0100341
342 switch (type) {
343 case FrameLoadTypeBackForward:
344 updateForBackForwardNavigation();
345 return;
346 case FrameLoadTypeReload:
347 case FrameLoadTypeReloadFromOrigin:
348 case FrameLoadTypeSame:
Torne (Richard Coles)81a51572013-05-13 16:52:28 +0100349 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)53e740f2013-05-09 18:38:43 +0100363}
364
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100365bool HistoryController::isReloadTypeWithProvisionalItem(FrameLoadType type)
366{
367 return (type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && m_provisionalItem;
368}
369
370void 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)f5e4ad52013-08-05 13:57:57 +0100379 // Save form state
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100380 if (m_currentItem && itemsAreClones(m_currentItem.get(), m_provisionalItem.get())) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100381 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)53e740f2013-05-09 18:38:43 +0100388 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
404void 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
419void 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)53e740f2013-05-09 18:38:43 +0100432 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)53e740f2013-05-09 18:38:43 +0100441void HistoryController::setCurrentItem(HistoryItem* item)
442{
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100443 m_previousItem = m_currentItem;
444 m_currentItem = item;
445}
446
447void 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
454bool 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
463void HistoryController::setProvisionalItem(HistoryItem* item)
464{
465 m_provisionalItem = item;
466}
467
468void 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 Murdoch02772c62013-07-26 10:21:05 +0100491 if (url.isEmpty())
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100492 url = blankURL();
493 if (originalURL.isEmpty())
494 originalURL = blankURL();
Ben Murdoch02772c62013-07-26 10:21:05 +0100495
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100496 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
511PassRefPtr<HistoryItem> HistoryController::createItem()
512{
513 RefPtr<HistoryItem> item = HistoryItem::create();
514 initializeItem(item.get());
Ben Murdoch02772c62013-07-26 10:21:05 +0100515
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100516 // Set the item for which we will save document state
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100517 m_previousItem = m_currentItem;
518 m_currentItem = item;
Ben Murdoch02772c62013-07-26 10:21:05 +0100519
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100520 return item.release();
521}
522
523PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget)
524{
525 RefPtr<HistoryItem> bfItem = createItem();
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100526 saveScrollPositionAndViewStateToItem(m_previousItem.get());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100527
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)53e740f2013-05-09 18:38:43 +0100543 // 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 Murdoch02772c62013-07-26 10:21:05 +0100546 FrameLoader* childLoader = child->loader();
547 if (childLoader->stateMachine()->startedFirstRealLoad() || !childLoader->isHostedByObjectElement())
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100548 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)81a51572013-05-13 16:52:28 +0100561void HistoryController::recursiveSetProvisionalItem(HistoryItem* item, HistoryItem* fromItem)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100562{
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)81a51572013-05-13 16:52:28 +0100579 childFrame->loader()->history()->recursiveSetProvisionalItem(childItems[i].get(), fromChildItem);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100580 }
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)81a51572013-05-13 16:52:28 +0100586void HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100587{
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)81a51572013-05-13 16:52:28 +0100601 childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100602 }
603 } else {
Ben Murdoch7757ec22013-07-23 11:17:36 +0100604 m_frame->loader()->loadHistoryItem(item);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100605 }
606}
607
608bool 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.
626bool 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 Murdoch02772c62013-07-26 10:21:05 +0100630
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100631 const HistoryItemVector& childItems = item->children();
632 if (childItems.size() != m_frame->tree()->childCount())
633 return false;
Ben Murdoch02772c62013-07-26 10:21:05 +0100634
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100635 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 Murdoch02772c62013-07-26 10:21:05 +0100640
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100641 return true;
642}
643
644void HistoryController::updateBackForwardListClippedAtTarget(bool doClip)
645{
Ben Murdoch02772c62013-07-26 10:21:05 +0100646 // 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)53e740f2013-05-09 18:38:43 +0100649 // 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
666void 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
691void 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 Murdoch02772c62013-07-26 10:21:05 +0100701
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100702 // 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
711void 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