Index: content/browser/frame_host/render_frame_host_manager_unittest.cc |
diff --git a/content/browser/frame_host/render_frame_host_manager_unittest.cc b/content/browser/frame_host/render_frame_host_manager_unittest.cc |
index d43c63fc23c91f69dcc3bd98f34885fce3894da5..72e8b3accc38dbd8bab9b008473666feab175525 100644 |
--- a/content/browser/frame_host/render_frame_host_manager_unittest.cc |
+++ b/content/browser/frame_host/render_frame_host_manager_unittest.cc |
@@ -125,6 +125,37 @@ class RenderViewHostDeletedObserver : public WebContentsObserver { |
}; |
+// This observer keeps track of the last deleted RenderViewHost to avoid |
+// accessing it and causing use-after-free condition. |
+class RenderFrameHostDeletedObserver : public WebContentsObserver { |
+ public: |
+ RenderFrameHostDeletedObserver(RenderFrameHost* rfh) |
+ : WebContentsObserver(WebContents::FromRenderFrameHost(rfh)), |
+ process_id_(rfh->GetProcess()->GetID()), |
+ routing_id_(rfh->GetRoutingID()), |
+ deleted_(false) { |
+ } |
+ |
+ virtual void RenderFrameDeleted(RenderFrameHost* render_frame_host) OVERRIDE { |
+ if (render_frame_host->GetProcess()->GetID() == process_id_ && |
+ render_frame_host->GetRoutingID() == routing_id_) { |
+ deleted_ = true; |
+ } |
+ } |
+ |
+ bool deleted() { |
+ return deleted_; |
+ } |
+ |
+ private: |
+ int process_id_; |
+ int routing_id_; |
+ bool deleted_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(RenderFrameHostDeletedObserver); |
+}; |
+ |
+ |
// This observer is used to check whether IPC messages are being filtered for |
// swapped out RenderFrameHost objects. It observes the plugin crash and favicon |
// update events, which the FilterMessagesWhileSwappedOut test simulates being |
@@ -1765,4 +1796,54 @@ TEST_F(RenderFrameHostManagerTest, |
EXPECT_TRUE(rvh1->IsSwappedOut()); |
} |
+// Test that a RenderFrameHost is properly deleted or swapped out when a |
+// cross-site navigation is cancelled. |
+TEST_F(RenderFrameHostManagerTest, |
+ CancelPendingProperlyDeletesOrSwaps) { |
+ const GURL kUrl1("http://www.google.com/"); |
+ const GURL kUrl2("http://www.chromium.org/"); |
+ RenderFrameHostImpl* pending_rfh = NULL; |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ |
+ // Navigate to the first page. |
+ contents()->NavigateAndCommit(kUrl1); |
+ TestRenderViewHost* rvh1 = test_rvh(); |
+ EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT, rvh1->rvh_state()); |
+ |
+ // Navigate to a new site, starting a cross-site navigation. |
+ controller().LoadURL(kUrl2, Referrer(), PAGE_TRANSITION_LINK, std::string()); |
+ { |
+ pending_rfh = contents()->GetFrameTree()->root()->render_manager() |
+ ->pending_frame_host(); |
+ RenderFrameHostDeletedObserver rvh_deleted_observer(pending_rfh); |
+ |
+ // Cancel the navigation by simulating a declined beforeunload dialog. |
+ main_test_rfh()->OnMessageReceived( |
+ FrameHostMsg_BeforeUnload_ACK(0, false, now, now)); |
+ EXPECT_FALSE(contents()->cross_navigation_pending()); |
+ |
+ // Since the pending RFH is the only one for the new SiteInstance, it should |
+ // be deleted. |
+ EXPECT_TRUE(rvh_deleted_observer.deleted()); |
+ } |
+ |
+ // Start another cross-site navigation. |
+ controller().LoadURL(kUrl2, Referrer(), PAGE_TRANSITION_LINK, std::string()); |
+ { |
+ pending_rfh = contents()->GetFrameTree()->root()->render_manager() |
+ ->pending_frame_host(); |
+ RenderFrameHostDeletedObserver rvh_deleted_observer(pending_rfh); |
+ |
+ // Increment the number of active views in the new SiteInstance, which will |
+ // cause the pending RFH to be swapped out instead of deleted. |
+ static_cast<SiteInstanceImpl*>( |
+ pending_rfh->GetSiteInstance())->increment_active_view_count(); |
+ |
+ main_test_rfh()->OnMessageReceived( |
+ FrameHostMsg_BeforeUnload_ACK(0, false, now, now)); |
+ EXPECT_FALSE(contents()->cross_navigation_pending()); |
+ EXPECT_FALSE(rvh_deleted_observer.deleted()); |
+ } |
+} |
+ |
} // namespace content |