};
+// 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
}
virtual void DidUpdateFaviconURL(
- int32 page_id,
const std::vector<FaviconURL>& candidates) OVERRIDE {
favicon_received_ = true;
}
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(ntp_rvh->OnMessageReceived(
ViewHostMsg_UpdateFaviconURL(
- rvh()->GetRoutingID(), 0, icons)));
+ rvh()->GetRoutingID(), icons)));
EXPECT_TRUE(observer.favicon_received());
}
- // Create one more view in the same SiteInstance where dest_rvh2
+ // Create one more view in the same SiteInstance where ntp_rvh
// exists so that it doesn't get deleted on navigation to another
// site.
static_cast<SiteInstanceImpl*>(ntp_rvh->GetSiteInstance())->
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(
dest_rvh->OnMessageReceived(
- ViewHostMsg_UpdateFaviconURL(rvh()->GetRoutingID(), 101, icons)));
+ ViewHostMsg_UpdateFaviconURL(rvh()->GetRoutingID(), icons)));
EXPECT_TRUE(observer.favicon_received());
}
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(
ntp_rvh->OnMessageReceived(
- ViewHostMsg_UpdateFaviconURL(rvh()->GetRoutingID(), 101, icons)));
+ ViewHostMsg_UpdateFaviconURL(rvh()->GetRoutingID(), icons)));
EXPECT_FALSE(observer.favicon_received());
}
MockRenderProcessHost* ntp_process_host =
static_cast<MockRenderProcessHost*>(ntp_rvh->GetProcess());
ntp_process_host->sink().ClearMessages();
+ RenderFrameHost* ntp_rfh = ntp_rvh->GetMainFrame();
const base::string16 msg = base::ASCIIToUTF16("Message");
bool result = false;
base::string16 unused;
- ViewHostMsg_RunBeforeUnloadConfirm before_unload_msg(
- rvh()->GetRoutingID(), kChromeURL, msg, false, &result, &unused);
+ FrameHostMsg_RunBeforeUnloadConfirm before_unload_msg(
+ ntp_rfh->GetRoutingID(), kChromeURL, msg, false, &result, &unused);
// Enable pumping for check in BrowserMessageFilter::CheckCanDispatchOnUI.
before_unload_msg.EnableMessagePumping();
- EXPECT_TRUE(ntp_rvh->OnMessageReceived(before_unload_msg));
+ EXPECT_TRUE(ntp_rfh->OnMessageReceived(before_unload_msg));
EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
// Also test RunJavaScriptMessage.
ntp_process_host->sink().ClearMessages();
- ViewHostMsg_RunJavaScriptMessage js_msg(
- rvh()->GetRoutingID(), msg, msg, kChromeURL,
+ FrameHostMsg_RunJavaScriptMessage js_msg(
+ ntp_rfh->GetRoutingID(), msg, msg, kChromeURL,
JAVASCRIPT_MESSAGE_TYPE_CONFIRM, &result, &unused);
js_msg.EnableMessagePumping();
- EXPECT_TRUE(ntp_rvh->OnMessageReceived(js_msg));
+ EXPECT_TRUE(ntp_rfh->OnMessageReceived(js_msg));
EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
}
notifications.Check1AndReset(NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
}
+// Test that navigation is not blocked when we make new navigation before
+// previous one has been committed. This is also a regression test for
+// http://crbug.com/104600.
+TEST_F(RenderFrameHostManagerTest, NewCrossNavigationBetweenSwapOutAndCommit) {
+ const GURL kUrl1("http://www.google.com/");
+ const GURL kUrl2("http://www.chromium.org/");
+ const GURL kUrl3("http://www.youtube.com/");
+
+ contents()->NavigateAndCommit(kUrl1);
+ TestRenderViewHost* rvh1 = test_rvh();
+
+ // Keep active_view_count nonzero so that no swapped out views in
+ // this SiteInstance get forcefully deleted.
+ static_cast<SiteInstanceImpl*>(rvh1->GetSiteInstance())->
+ increment_active_view_count();
+
+ // Navigate but don't commit.
+ contents()->GetController().LoadURL(
+ kUrl2, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ EXPECT_TRUE(rvh1->is_waiting_for_beforeunload_ack());
+ contents()->ProceedWithCrossSiteNavigation();
+ EXPECT_FALSE(rvh1->is_waiting_for_beforeunload_ack());
+ StartCrossSiteTransition(contents());
+ EXPECT_TRUE(rvh1->IsWaitingForUnloadACK());
+
+ rvh1->OnSwappedOut(false);
+ EXPECT_EQ(RenderViewHostImpl::STATE_WAITING_FOR_COMMIT, rvh1->rvh_state());
+
+ TestRenderViewHost* rvh2 = pending_test_rvh();
+ EXPECT_TRUE(rvh2);
+ static_cast<SiteInstanceImpl*>(rvh2->GetSiteInstance())->
+ increment_active_view_count();
+
+ contents()->GetController().LoadURL(
+ kUrl3, Referrer(), PAGE_TRANSITION_LINK, std::string());
+ // Pending rvh2 is already deleted.
+ contents()->ProceedWithCrossSiteNavigation();
+
+ TestRenderViewHost* rvh3 = pending_test_rvh();
+ EXPECT_TRUE(rvh3);
+ // Navigation should be already unblocked by rvh1.
+ EXPECT_FALSE(rvh3->are_navigations_suspended());
+}
+
// Tests WebUI creation.
TEST_F(RenderFrameHostManagerTest, WebUI) {
set_should_create_webui(true);
EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSiteURL());
// The Web UI is committed immediately because the RenderViewHost has not been
- // used yet. UpdateRendererStateForNavigate() took the short cut path.
+ // used yet. UpdateStateForNavigate() took the short cut path.
EXPECT_FALSE(manager->pending_web_ui());
EXPECT_TRUE(manager->web_ui());
manager1->Init(
browser_context(), blank_instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
// Test the case that new RVH is considered live.
- manager1->current_host()->CreateRenderView(base::string16(), -1, -1);
+ manager1->current_host()->CreateRenderView(base::string16(), -1, -1, false);
// Navigate to a WebUI page.
const GURL kUrl1("chrome://foo");
browser_context(), webui_instance, MSG_ROUTING_NONE, MSG_ROUTING_NONE);
// Make sure the new RVH is considered live. This is usually done in
// RenderWidgetHost::Init when opening a new tab from a link.
- manager2->current_host()->CreateRenderView(base::string16(), -1, -1);
+ manager2->current_host()->CreateRenderView(base::string16(), -1, -1, false);
const GURL kUrl2("chrome://foo/bar");
NavigationEntryImpl entry2(NULL /* instance */, -1 /* page_id */, kUrl2,
contents()->SetOpener(opener1.get());
// Make sure the new opener RVH is considered live.
- opener1_manager->current_host()->CreateRenderView(base::string16(), -1, -1);
+ opener1_manager->current_host()->CreateRenderView(
+ base::string16(), -1, -1, false);
// Use a cross-process navigation in the opener to swap out the old RVH.
EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
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