Bug 50254 - Canceled frame loads can corrupt back forward list
Summary: Canceled frame loads can corrupt back forward list
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Frames (show other bugs)
Version: 528+ (Nightly build)
Hardware: All All
: P2 Normal
Assignee: Nobody
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-11-30 11:19 PST by Charles Reis
Modified: 2011-01-08 17:27 PST (History)
4 users (show)

See Also:


Attachments
Forward and cancel frame test (1.68 KB, application/zip)
2010-11-30 11:19 PST, Charles Reis
no flags Details
Patch (8.29 KB, patch)
2010-12-15 16:57 PST, Charles Reis
no flags Details | Formatted Diff | Diff
Patch (14.57 KB, patch)
2011-01-05 15:45 PST, Charles Reis
no flags Details | Formatted Diff | Diff
Patch (15.99 KB, patch)
2011-01-06 15:25 PST, Charles Reis
no flags Details | Formatted Diff | Diff
Patch (16.12 KB, patch)
2011-01-07 09:57 PST, Charles Reis
no flags Details | Formatted Diff | Diff
Patch (16.03 KB, patch)
2011-01-07 11:39 PST, Charles Reis
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Charles Reis 2010-11-30 11:19:43 PST
Created attachment 75173 [details]
Forward and cancel frame test

If a page load is canceled, FrameLoader::checkLoadCompleteForThisFrame resets the back forward list to the previously committed history item, using:
  page->backForward()->setCurrentItem(item.get());

However, this logic only runs if the canceled page load is a main frame.  If a frame load is canceled, the back forward list is left pointing to the canceled item.  This leads to incorrect behavior if the user goes back or forward.

The attached test case can demonstrate this if copied to LayoutTests/http/tests/navigation/.  Going back after canceling the frame load copies the 3rd entry into the 2nd slot.

Not sure what the fix is yet, since calling setCurrentItem on subframe loads doesn't help.
Comment 1 Charles Reis 2010-12-15 16:57:54 PST
Created attachment 76713 [details]
Patch
Comment 2 Darin Fisher (:fishd, Google) 2010-12-21 13:52:33 PST
Comment on attachment 76713 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=76713&action=review

> WebCore/loader/FrameLoader.cpp:2390
> +                        page->mainFrame()->loader()->history()->cancelCurrentItem();

The name cancelCurrentItem is not so great.  You aren't really cancelling it.
You are cancelling / reverting the switch to it.

Also, it seems like you might need to cancel more than just the current item
of the top-level frame.  What if m_frame is actually a grandchild of the main
frame?  What about other sibling frames that aren't navigating?  R- because
of this issue.

I wonder if we shouldn't be making use of HistoryController::m_provisionalItem
more.  It seems like we shouldn't commit to changing m_currentItem until we
know that it is going to stick.  Although that might be a much larger change :-/
Comment 3 Charles Reis 2011-01-05 11:42:00 PST
I'm trying a new approach with this bug.  I agree that we shouldn't be updating m_currentItem until the navigation is ready to commit, so it seems like recursiveGoToItem needs to change.  I'm taking your suggestion and setting m_provisionalItem in each of the frames of the tree until the commit, instead.

There's one wrinkle here that makes it more complicated.  While we are walking the frame tree with recursiveGoToItem and setting provisional items, it is possible for the actual navigation to immediately commit (on URLs like about:blank) *before* we visit the rest of the frames.  This means that a series of commit related events like updateForCommit, updateForFrameLoadCompleted, and the SetContentState calls in TestShell can all happen before we've set the provisional items on the remaining frames in the tree.

To clean this up, I'll split recursiveGoToItem into two phases: one that sets provisional items on all the frames that aren't going anywhere, and a second that navigates the desired frame(s).  As a result, we'll be ready if the navigation commits immediately.

I'll also have to fix updateForCommit to walk the frame tree and commit each of the provisional items afterward.  This will also restore their scroll position, instead of doing it in recursiveGoToItem.

I have a draft of a patch that seems to be working, and I'll upload it once it's cleaned up and passing all the tests.
Comment 4 Charles Reis 2011-01-05 15:45:28 PST
Created attachment 78057 [details]
Patch
Comment 5 Mihai Parparita 2011-01-06 10:57:40 PST
Comment on attachment 78057 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=78057&action=review

> WebCore/loader/HistoryController.cpp:243
> +    recursiveGoToProvisionalItem(targetItem, currentItem, type);

recursiveSetProvisionalItem seems like a more appropriate name, since it doesn't actually do any navigating/"going."

> WebCore/loader/HistoryController.cpp:423
> +    if (m_provisionalItem) {

Seems better to have a !m_provisionalItem early return, so that the rest function body isn't indented by an extra level.

> WebCore/loader/HistoryController.cpp:601
> +// a match (by URL), we set the provisional item and recurse.  Otherwise we will reload

This comment should probably be updated or removed, we no longer match by URL (it's by item sequence number and frame name now).

> WebCore/loader/HistoryController.cpp:643
> +    if (item != fromItem 

The logic to see if the items/frames match seems complex enough that it shouldn't be repeated in recursiveGoToProvisionalItem and recursiveGoToItem, and instead moved to a itemTransitionNeedsLoad(item1, item2) helper method (possibly with a better name than that).

> LayoutTests/http/tests/navigation/forward-and-cancel.html:16
> +    layoutTestController.queueLoad("javascript:frames[0].clickLink();");

Do you actually need to use the javascript: URI scheme, or can this be queueNonLoadingScript("frames[0].clickLink()")?

> LayoutTests/http/tests/navigation/forward-and-cancel.html:27
> +    layoutTestController.queueLoadingScript("layoutTestController.waitUntilDone();");

Do you need to use queueLoadingScript, or can you just call layoutTestController.waitUntilDone() directly at the top, where you call dumpAsText and other setup code?
Comment 6 Charles Reis 2011-01-06 15:25:06 PST
Comment on attachment 78057 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=78057&action=review

Thanks for the feedback.  New patch on the way!

>> WebCore/loader/HistoryController.cpp:243
>> +    recursiveGoToProvisionalItem(targetItem, currentItem, type);
> 
> recursiveSetProvisionalItem seems like a more appropriate name, since it doesn't actually do any navigating/"going."

Done.

>> WebCore/loader/HistoryController.cpp:423
>> +    if (m_provisionalItem) {
> 
> Seems better to have a !m_provisionalItem early return, so that the rest function body isn't indented by an extra level.

Done.

>> WebCore/loader/HistoryController.cpp:601
>> +// a match (by URL), we set the provisional item and recurse.  Otherwise we will reload
> 
> This comment should probably be updated or removed, we no longer match by URL (it's by item sequence number and frame name now).

Done.

>> WebCore/loader/HistoryController.cpp:643
>> +    if (item != fromItem 
> 
> The logic to see if the items/frames match seems complex enough that it shouldn't be repeated in recursiveGoToProvisionalItem and recursiveGoToItem, and instead moved to a itemTransitionNeedsLoad(item1, item2) helper method (possibly with a better name than that).

Good idea.  Done.

>> LayoutTests/http/tests/navigation/forward-and-cancel.html:16
>> +    layoutTestController.queueLoad("javascript:frames[0].clickLink();");
> 
> Do you actually need to use the javascript: URI scheme, or can this be queueNonLoadingScript("frames[0].clickLink()")?

It needs to be queueLoadingScript, but yes, that's better.

>> LayoutTests/http/tests/navigation/forward-and-cancel.html:27
>> +    layoutTestController.queueLoadingScript("layoutTestController.waitUntilDone();");
> 
> Do you need to use queueLoadingScript, or can you just call layoutTestController.waitUntilDone() directly at the top, where you call dumpAsText and other setup code?

I can't seem to get it to work if I call waitUntilDone() directly-- it always hangs on the first page without doing any of the queued navigations.

I did make the notifyDone() call only happen on the second visit to the page, but that didn't help.
Comment 7 Charles Reis 2011-01-06 15:25:28 PST
Created attachment 78175 [details]
Patch
Comment 8 Mihai Parparita 2011-01-06 18:51:50 PST
Comment on attachment 78175 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=78175&action=review

> WebCore/loader/HistoryController.cpp:657
> +    // Note: If item1 and item2 are the same, then we need to create a new

This comment (which I realize predates your patch, you just moved it around) always bothered me, since it's just stating what the code does, but doesn't explain why that behavior is desired/necessary.  Based on http://webkit.org/b/35532 (which is the oldest reference to it that I can find), it seems like some WebKit clients use navigation to the current history item as a reload mechanism. Perhaps you can mention that (and/or the bug number) so that the comment provides some justification for the behavior?
Comment 9 Charles Reis 2011-01-07 09:56:47 PST
Comment on attachment 78175 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=78175&action=review

New patch ready for review.

>> WebCore/loader/HistoryController.cpp:657
>> +    // Note: If item1 and item2 are the same, then we need to create a new
> 
> This comment (which I realize predates your patch, you just moved it around) always bothered me, since it's just stating what the code does, but doesn't explain why that behavior is desired/necessary.  Based on http://webkit.org/b/35532 (which is the oldest reference to it that I can find), it seems like some WebKit clients use navigation to the current history item as a reload mechanism. Perhaps you can mention that (and/or the bug number) so that the comment provides some justification for the behavior?

Thanks for figuring out why it's the case!  I've updated the comment as you suggest.
Comment 10 Charles Reis 2011-01-07 09:57:08 PST
Created attachment 78246 [details]
Patch
Comment 11 Mihai Parparita 2011-01-07 10:18:44 PST
Comment on attachment 78246 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=78246&action=review

You should also rebaseline your patch against ToT, otherwise the commit queue may have trouble landing your change (at least the review tool can't apply your changes to FrameLoader.cpp to tip of tree).

> WebCore/loader/HistoryController.cpp:657
> +    // Note: Some clients treat a navigatation to the current history item as

Typo (navigatation)
Comment 12 Charles Reis 2011-01-07 11:39:54 PST
Created attachment 78252 [details]
Patch
Comment 13 WebKit Commit Bot 2011-01-08 17:27:34 PST
Comment on attachment 78252 [details]
Patch

Clearing flags on attachment: 78252

Committed r75336: <http://trac.webkit.org/changeset/75336>
Comment 14 WebKit Commit Bot 2011-01-08 17:27:39 PST
All reviewed patches have been landed.  Closing bug.