Index: content/browser/frame_host/navigation_controller_impl_browsertest.cc |
diff --git a/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/content/browser/frame_host/navigation_controller_impl_browsertest.cc |
index 5dec7a88a0665071b5a302f8e3eb215b62b0cda2..3d564f92f8fba87cc70ec768b8597349d9f08a75 100644 |
--- a/content/browser/frame_host/navigation_controller_impl_browsertest.cc |
+++ b/content/browser/frame_host/navigation_controller_impl_browsertest.cc |
@@ -17,6 +17,7 @@ |
#include "content/browser/frame_host/navigation_entry_impl.h" |
#include "content/browser/loader/resource_dispatcher_host_impl.h" |
#include "content/browser/web_contents/web_contents_impl.h" |
+#include "content/common/page_state_serialization.h" |
#include "content/common/site_isolation_policy.h" |
#include "content/public/browser/navigation_handle.h" |
#include "content/public/browser/render_view_host.h" |
@@ -3520,4 +3521,149 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, |
EXPECT_EQ(frame_url_1, frame->current_url()); |
} |
+// Ensure that we do not corrupt a NavigationEntry's PageState if a subframe |
+// forward navigation commits after we've already started another forward |
+// navigation in the main frame. See https://crbug.com/597322. |
+IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, |
+ ForwardInSubframeWithPendingForward) { |
+ // Navigate to a page with an iframe. |
+ GURL url_a(embedded_test_server()->GetURL( |
+ "/navigation_controller/page_with_data_iframe.html")); |
+ GURL frame_url_a1("data:text/html,Subframe"); |
+ EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
+ |
+ NavigationController& controller = shell()->web_contents()->GetController(); |
+ FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
+ ->GetFrameTree() |
+ ->root(); |
+ ASSERT_EQ(1U, root->child_count()); |
+ EXPECT_EQ(url_a, root->current_url()); |
+ FrameTreeNode* frame = root->child_at(0); |
+ EXPECT_EQ(frame_url_a1, frame->current_url()); |
+ |
+ // Navigate the iframe to a second page. |
+ GURL frame_url_a2 = embedded_test_server()->GetURL( |
+ "/navigation_controller/simple_page_1.html"); |
+ NavigateFrameToURL(frame, frame_url_a2); |
+ |
+ EXPECT_EQ(2, controller.GetEntryCount()); |
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex()); |
+ EXPECT_EQ(url_a, root->current_url()); |
+ EXPECT_EQ(frame_url_a2, frame->current_url()); |
+ |
+ // Navigate the top-level frame to another page with an iframe. |
+ GURL url_b(embedded_test_server()->GetURL( |
+ "/navigation_controller/page_with_iframe.html")); |
+ GURL frame_url_b1(url::kAboutBlankURL); |
+ EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
+ EXPECT_EQ(3, controller.GetEntryCount()); |
+ EXPECT_EQ(url_b, root->current_url()); |
+ EXPECT_EQ(frame_url_b1, root->child_at(0)->current_url()); |
+ |
+ // Go back two entries. The original frame URL should be back. |
+ ASSERT_TRUE(controller.CanGoToOffset(-2)); |
+ controller.GoToOffset(-2); |
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
+ |
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); |
+ EXPECT_EQ(url_a, root->current_url()); |
+ EXPECT_EQ(frame_url_a1, root->child_at(0)->current_url()); |
+ |
+ // Go forward two times in a row, being careful that the subframe commits |
+ // after the second forward navigation begins but before the main frame |
+ // commits. |
+ TestNavigationManager subframe_delayer(shell()->web_contents(), frame_url_a2); |
+ TestNavigationManager mainframe_delayer(shell()->web_contents(), url_b); |
+ controller.GoForward(); |
+ subframe_delayer.WaitForWillStartRequest(); |
+ controller.GoForward(); |
+ mainframe_delayer.WaitForWillStartRequest(); |
+ EXPECT_EQ(2, controller.GetPendingEntryIndex()); |
+ |
+ // Let the subframe commit. |
+ subframe_delayer.ResumeNavigation(); |
+ subframe_delayer.WaitForNavigationFinished(); |
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex()); |
+ EXPECT_EQ(url_a, root->current_url()); |
+ EXPECT_EQ(frame_url_a2, root->child_at(0)->current_url()); |
+ |
+ // Let the main frame commit. |
+ mainframe_delayer.ResumeNavigation(); |
+ mainframe_delayer.WaitForNavigationFinished(); |
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
+ EXPECT_EQ(2, controller.GetLastCommittedEntryIndex()); |
+ EXPECT_EQ(url_b, root->current_url()); |
+ EXPECT_EQ(frame_url_b1, root->child_at(0)->current_url()); |
+ |
+ // Check the PageState of the previous entry to ensure it isn't corrupted. |
+ NavigationEntry* entry = controller.GetEntryAtIndex(1); |
+ EXPECT_EQ(url_a, entry->GetURL()); |
+ ExplodedPageState exploded_state; |
+ EXPECT_TRUE( |
+ DecodePageState(entry->GetPageState().ToEncodedData(), &exploded_state)); |
+ EXPECT_EQ(url_a, GURL(exploded_state.top.url_string.string())); |
+ EXPECT_EQ(frame_url_a2, |
+ GURL(exploded_state.top.children.at(0).url_string.string())); |
+} |
+ |
+// Ensure that forward navigations in cloned tabs can commit if they redirect to |
+// a different site than before. This causes the navigation's item sequence |
+// number to change, meaning that we can't use it for determining whether the |
+// commit matches the history item. See https://crbug.com/600238. |
+IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest, |
+ ForwardRedirectWithNoCommittedEntry) { |
+ NavigationController& controller = shell()->web_contents()->GetController(); |
+ FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
+ ->GetFrameTree() |
+ ->root(); |
+ |
+ // Put 2 pages in history. |
+ GURL url_1(embedded_test_server()->GetURL( |
+ "/navigation_controller/simple_page_1.html")); |
+ EXPECT_TRUE(NavigateToURL(shell(), url_1)); |
+ |
+ GURL url_2(embedded_test_server()->GetURL( |
+ "/navigation_controller/simple_page_2.html")); |
+ EXPECT_TRUE(NavigateToURL(shell(), url_2)); |
+ |
+ EXPECT_EQ(url_2, root->current_url()); |
+ EXPECT_EQ(2, controller.GetEntryCount()); |
+ EXPECT_EQ(1, controller.GetLastCommittedEntryIndex()); |
+ |
+ // Do a replaceState to a URL that will redirect when we come back to it via |
+ // session history. |
+ GURL url_3(embedded_test_server()->GetURL( |
+ "foo.com", "/navigation_controller/page_with_links.html")); |
+ { |
+ TestNavigationObserver observer(shell()->web_contents()); |
+ std::string script = |
+ "history.replaceState({}, '', '/server-redirect?" + url_3.spec() + "')"; |
+ EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script)); |
+ observer.Wait(); |
+ } |
+ |
+ // Go back. |
+ controller.GoBack(); |
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
+ EXPECT_EQ(0, controller.GetLastCommittedEntryIndex()); |
+ EXPECT_EQ(url_1, root->current_url()); |
+ |
+ // Clone the tab without navigating it. |
+ std::unique_ptr<WebContentsImpl> new_tab( |
+ static_cast<WebContentsImpl*>(shell()->web_contents()->Clone())); |
+ NavigationController& new_controller = new_tab->GetController(); |
+ FrameTreeNode* new_root = new_tab->GetFrameTree()->root(); |
+ EXPECT_TRUE(new_controller.IsInitialNavigation()); |
+ EXPECT_TRUE(new_controller.NeedsReload()); |
+ |
+ // Go forward in the new tab. |
+ { |
+ TestNavigationObserver observer(new_tab.get()); |
+ new_controller.GoForward(); |
+ observer.Wait(); |
+ } |
+ EXPECT_TRUE(new_root->current_frame_host()->IsRenderFrameLive()); |
+ EXPECT_EQ(url_3, new_root->current_url()); |
+} |
+ |
} // namespace content |