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