| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/utf_string_conversions.h" |
| #include "content/browser/browser_thread_impl.h" |
| #include "content/browser/mock_content_browser_client.h" |
| #include "content/browser/renderer_host/test_render_view_host.h" |
| #include "content/browser/site_instance_impl.h" |
| #include "content/browser/web_contents/navigation_entry_impl.h" |
| #include "content/browser/web_contents/navigation_controller_impl.h" |
| #include "content/browser/web_contents/render_view_host_manager.h" |
| #include "content/browser/web_contents/test_web_contents.h" |
| #include "content/common/test_url_constants.h" |
| #include "content/common/view_messages.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/web_ui_controller.h" |
| #include "content/public/browser/web_ui_controller_factory.h" |
| #include "content/public/common/page_transition_types.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/test/mock_render_process_host.h" |
| #include "content/test/test_browser_context.h" |
| #include "content/test/test_content_client.h" |
| #include "content/test/test_notification_tracker.h" |
| #include "googleurl/src/url_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/javascript_message_type.h" |
| #include "webkit/glue/webkit_glue.h" |
| |
| using content::BrowserContext; |
| using content::BrowserThread; |
| using content::BrowserThreadImpl; |
| using content::MockRenderProcessHost; |
| using content::NavigationController; |
| using content::NavigationEntry; |
| using content::NavigationEntryImpl; |
| using content::RenderViewHost; |
| using content::RenderViewHostImpl; |
| using content::RenderViewHostImplTestHarness; |
| using content::SiteInstance; |
| using content::TestRenderViewHost; |
| using content::TestWebContents; |
| using content::WebContents; |
| using content::WebUI; |
| using content::WebUIController; |
| |
| namespace { |
| |
| class RenderViewHostManagerTestWebUIControllerFactory |
| : public content::WebUIControllerFactory { |
| public: |
| RenderViewHostManagerTestWebUIControllerFactory() |
| : should_create_webui_(false) { |
| } |
| virtual ~RenderViewHostManagerTestWebUIControllerFactory() {} |
| |
| void set_should_create_webui(bool should_create_webui) { |
| should_create_webui_ = should_create_webui; |
| } |
| |
| // WebUIFactory implementation. |
| virtual WebUIController* CreateWebUIControllerForURL( |
| WebUI* web_ui, const GURL& url) const OVERRIDE { |
| if (!(should_create_webui_ && |
| content::GetContentClient()->HasWebUIScheme(url))) |
| return NULL; |
| return new WebUIController(web_ui); |
| } |
| |
| virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context, |
| const GURL& url) const OVERRIDE { |
| return WebUI::kNoWebUI; |
| } |
| |
| virtual bool UseWebUIForURL(BrowserContext* browser_context, |
| const GURL& url) const OVERRIDE { |
| return content::GetContentClient()->HasWebUIScheme(url); |
| } |
| |
| virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context, |
| const GURL& url) const OVERRIDE { |
| return content::GetContentClient()->HasWebUIScheme(url); |
| } |
| |
| virtual bool IsURLAcceptableForWebUI( |
| BrowserContext* browser_context, |
| const GURL& url, |
| bool data_urls_allowed) const OVERRIDE { |
| return false; |
| } |
| |
| private: |
| bool should_create_webui_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderViewHostManagerTestWebUIControllerFactory); |
| }; |
| |
| class RenderViewHostManagerTestClient : public TestContentClient { |
| public: |
| RenderViewHostManagerTestClient() { |
| } |
| |
| virtual bool HasWebUIScheme(const GURL& url) const OVERRIDE { |
| return url.SchemeIs(chrome::kChromeUIScheme); |
| } |
| }; |
| |
| class RenderViewHostManagerTestBrowserClient |
| : public content::MockContentBrowserClient { |
| public: |
| RenderViewHostManagerTestBrowserClient() {} |
| virtual ~RenderViewHostManagerTestBrowserClient() {} |
| |
| void set_should_create_webui(bool should_create_webui) { |
| factory_.set_should_create_webui(should_create_webui); |
| } |
| |
| // content::MockContentBrowserClient implementation. |
| virtual content::WebUIControllerFactory* |
| GetWebUIControllerFactory() OVERRIDE { |
| return &factory_; |
| } |
| |
| private: |
| RenderViewHostManagerTestWebUIControllerFactory factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderViewHostManagerTestBrowserClient); |
| }; |
| |
| } // namespace |
| |
| class RenderViewHostManagerTest |
| : public RenderViewHostImplTestHarness { |
| public: |
| virtual void SetUp() OVERRIDE { |
| RenderViewHostImplTestHarness::SetUp(); |
| old_client_ = content::GetContentClient(); |
| old_browser_client_ = content::GetContentClient()->browser(); |
| content::SetContentClient(&client_); |
| content::GetContentClient()->set_browser(&browser_client_); |
| url_util::AddStandardScheme(chrome::kChromeUIScheme); |
| } |
| |
| virtual void TearDown() OVERRIDE { |
| RenderViewHostImplTestHarness::TearDown(); |
| content::GetContentClient()->set_browser(old_browser_client_); |
| content::SetContentClient(old_client_); |
| } |
| |
| void set_should_create_webui(bool should_create_webui) { |
| browser_client_.set_should_create_webui(should_create_webui); |
| } |
| |
| void NavigateActiveAndCommit(const GURL& url) { |
| // Note: we navigate the active RenderViewHost because previous navigations |
| // won't have committed yet, so NavigateAndCommit does the wrong thing |
| // for us. |
| controller().LoadURL( |
| url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); |
| TestRenderViewHost* old_rvh = test_rvh(); |
| |
| // Simulate the ShouldClose_ACK that is received from the current renderer |
| // for a cross-site navigation. |
| if (old_rvh != active_rvh()) |
| old_rvh->SendShouldCloseACK(true); |
| |
| // Commit the navigation with a new page ID. |
| int32 max_page_id = contents()->GetMaxPageIDForSiteInstance( |
| active_rvh()->GetSiteInstance()); |
| active_test_rvh()->SendNavigate(max_page_id + 1, url); |
| |
| // Simulate the SwapOut_ACK that fires if you commit a cross-site navigation |
| // without making any network requests. |
| if (old_rvh != active_rvh()) |
| old_rvh->OnSwapOutACK(false); |
| } |
| |
| bool ShouldSwapProcesses(RenderViewHostManager* manager, |
| const NavigationEntryImpl* cur_entry, |
| const NavigationEntryImpl* new_entry) const { |
| return manager->ShouldSwapProcessesForNavigation(cur_entry, new_entry); |
| } |
| |
| private: |
| RenderViewHostManagerTestClient client_; |
| RenderViewHostManagerTestBrowserClient browser_client_; |
| content::ContentClient* old_client_; |
| content::ContentBrowserClient* old_browser_client_; |
| }; |
| |
| // Tests that when you navigate from the New TabPage to another page, and |
| // then do that same thing in another tab, that the two resulting pages have |
| // different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is |
| // a regression test for bug 9364. |
| TEST_F(RenderViewHostManagerTest, NewTabPageProcesses) { |
| BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); |
| const GURL kNtpUrl(chrome::kTestNewTabURL); |
| const GURL kDestUrl("http://www.google.com/"); |
| |
| // Navigate our first tab to the new tab page and then to the destination. |
| NavigateActiveAndCommit(kNtpUrl); |
| NavigateActiveAndCommit(kDestUrl); |
| |
| // Make a second tab. |
| TestWebContents contents2(browser_context(), NULL); |
| |
| // Load the two URLs in the second tab. Note that the first navigation creates |
| // a RVH that's not pending (since there is no cross-site transition), so |
| // we use the committed one. |
| contents2.GetController().LoadURL( |
| kNtpUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, |
| std::string()); |
| TestRenderViewHost* ntp_rvh2 = static_cast<TestRenderViewHost*>( |
| contents2.GetRenderManagerForTesting()->current_host()); |
| EXPECT_FALSE(contents2.cross_navigation_pending()); |
| ntp_rvh2->SendNavigate(100, kNtpUrl); |
| |
| // The second one is the opposite, creating a cross-site transition and |
| // requiring a beforeunload ack. |
| contents2.GetController().LoadURL( |
| kDestUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, |
| std::string()); |
| EXPECT_TRUE(contents2.cross_navigation_pending()); |
| TestRenderViewHost* dest_rvh2 = static_cast<TestRenderViewHost*>( |
| contents2.GetRenderManagerForTesting()->pending_render_view_host()); |
| ASSERT_TRUE(dest_rvh2); |
| ntp_rvh2->SendShouldCloseACK(true); |
| dest_rvh2->SendNavigate(101, kDestUrl); |
| ntp_rvh2->OnSwapOutACK(false); |
| |
| // The two RVH's should be different in every way. |
| EXPECT_NE(active_rvh()->GetProcess(), dest_rvh2->GetProcess()); |
| EXPECT_NE(active_rvh()->GetSiteInstance(), dest_rvh2->GetSiteInstance()); |
| EXPECT_NE(static_cast<SiteInstanceImpl*>(active_rvh()->GetSiteInstance())-> |
| browsing_instance_, |
| static_cast<SiteInstanceImpl*>(dest_rvh2->GetSiteInstance())-> |
| browsing_instance_); |
| |
| // Navigate both to the new tab page, and verify that they share a |
| // SiteInstance. |
| NavigateActiveAndCommit(kNtpUrl); |
| |
| contents2.GetController().LoadURL( |
| kNtpUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, |
| std::string()); |
| dest_rvh2->SendShouldCloseACK(true); |
| static_cast<TestRenderViewHost*>(contents2.GetRenderManagerForTesting()-> |
| pending_render_view_host())->SendNavigate(102, kNtpUrl); |
| dest_rvh2->OnSwapOutACK(false); |
| |
| EXPECT_EQ(active_rvh()->GetSiteInstance(), |
| contents2.GetRenderViewHost()->GetSiteInstance()); |
| } |
| |
| // Ensure that the browser ignores most IPC messages that arrive from a |
| // RenderViewHost that has been swapped out. We do not want to take |
| // action on requests from a non-active renderer. The main exception is |
| // for synchronous messages, which cannot be ignored without leaving the |
| // renderer in a stuck state. See http://crbug.com/93427. |
| TEST_F(RenderViewHostManagerTest, FilterMessagesWhileSwappedOut) { |
| BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); |
| const GURL kNtpUrl(chrome::kTestNewTabURL); |
| const GURL kDestUrl("http://www.google.com/"); |
| |
| // Navigate our first tab to the new tab page and then to the destination. |
| NavigateActiveAndCommit(kNtpUrl); |
| TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>( |
| contents()->GetRenderManagerForTesting()->current_host()); |
| |
| // Send an update title message and make sure it works. |
| const string16 ntp_title = ASCIIToUTF16("NTP Title"); |
| WebKit::WebTextDirection direction = WebKit::WebTextDirectionLeftToRight; |
| EXPECT_TRUE(ntp_rvh->OnMessageReceived( |
| ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction))); |
| EXPECT_EQ(ntp_title, contents()->GetTitle()); |
| |
| // Navigate to a cross-site URL. |
| contents()->GetController().LoadURL( |
| kDestUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, |
| std::string()); |
| EXPECT_TRUE(contents()->cross_navigation_pending()); |
| TestRenderViewHost* dest_rvh = static_cast<TestRenderViewHost*>( |
| contents()->GetRenderManagerForTesting()->pending_render_view_host()); |
| ASSERT_TRUE(dest_rvh); |
| EXPECT_NE(ntp_rvh, dest_rvh); |
| |
| // BeforeUnload finishes. |
| ntp_rvh->SendShouldCloseACK(true); |
| |
| // Assume SwapOutACK times out, so the dest_rvh proceeds and commits. |
| dest_rvh->SendNavigate(101, kDestUrl); |
| |
| // The new RVH should be able to update its title. |
| const string16 dest_title = ASCIIToUTF16("Google"); |
| EXPECT_TRUE(dest_rvh->OnMessageReceived( |
| ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 101, dest_title, |
| direction))); |
| EXPECT_EQ(dest_title, contents()->GetTitle()); |
| |
| // The old renderer, being slow, now updates the title. It should be filtered |
| // out and not take effect. |
| EXPECT_TRUE(ntp_rvh->is_swapped_out()); |
| EXPECT_TRUE(ntp_rvh->OnMessageReceived( |
| ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction))); |
| EXPECT_EQ(dest_title, contents()->GetTitle()); |
| |
| // We cannot filter out synchronous IPC messages, because the renderer would |
| // be left waiting for a reply. We pick RunBeforeUnloadConfirm as an example |
| // that can run easily within a unit test, and that needs to receive a reply |
| // without showing an actual dialog. |
| MockRenderProcessHost* ntp_process_host = |
| static_cast<MockRenderProcessHost*>(ntp_rvh->GetProcess()); |
| ntp_process_host->sink().ClearMessages(); |
| const string16 msg = ASCIIToUTF16("Message"); |
| bool result = false; |
| string16 unused; |
| ViewHostMsg_RunBeforeUnloadConfirm before_unload_msg( |
| rvh()->GetRoutingID(), kNtpUrl, 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_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID)); |
| |
| // Also test RunJavaScriptMessage. |
| ntp_process_host->sink().ClearMessages(); |
| ViewHostMsg_RunJavaScriptMessage js_msg( |
| rvh()->GetRoutingID(), msg, msg, kNtpUrl, |
| ui::JAVASCRIPT_MESSAGE_TYPE_CONFIRM, &result, &unused); |
| js_msg.EnableMessagePumping(); |
| EXPECT_TRUE(ntp_rvh->OnMessageReceived(js_msg)); |
| EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID)); |
| } |
| |
| // When there is an error with the specified page, renderer exits view-source |
| // mode. See WebFrameImpl::DidFail(). We check by this test that |
| // EnableViewSourceMode message is sent on every navigation regardless |
| // RenderView is being newly created or reused. |
| TEST_F(RenderViewHostManagerTest, AlwaysSendEnableViewSourceMode) { |
| BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); |
| const GURL kNtpUrl(chrome::kTestNewTabURL); |
| const GURL kUrl("view-source:http://foo"); |
| |
| // We have to navigate to some page at first since without this, the first |
| // navigation will reuse the SiteInstance created by Init(), and the second |
| // one will create a new SiteInstance. Because current_instance and |
| // new_instance will be different, a new RenderViewHost will be created for |
| // the second navigation. We have to avoid this in order to exercise the |
| // target code patch. |
| NavigateActiveAndCommit(kNtpUrl); |
| |
| // Navigate. |
| controller().LoadURL( |
| kUrl, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string()); |
| // Simulate response from RenderView for FirePageBeforeUnload. |
| test_rvh()->OnMessageReceived(ViewHostMsg_ShouldClose_ACK( |
| rvh()->GetRoutingID(), true, base::TimeTicks(), base::TimeTicks())); |
| ASSERT_TRUE(pending_rvh()); // New pending RenderViewHost will be created. |
| RenderViewHost* last_rvh = pending_rvh(); |
| int32 new_id = contents()->GetMaxPageIDForSiteInstance( |
| active_rvh()->GetSiteInstance()) + 1; |
| pending_test_rvh()->SendNavigate(new_id, kUrl); |
| EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1); |
| ASSERT_TRUE(controller().GetLastCommittedEntry()); |
| EXPECT_TRUE(kUrl == controller().GetLastCommittedEntry()->GetURL()); |
| EXPECT_FALSE(controller().GetPendingEntry()); |
| // Because we're using TestWebContents and TestRenderViewHost in this |
| // unittest, no one calls WebContentsImpl::RenderViewCreated(). So, we see no |
| // EnableViewSourceMode message, here. |
| |
| // Clear queued messages before load. |
| process()->sink().ClearMessages(); |
| // Navigate, again. |
| controller().LoadURL( |
| kUrl, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string()); |
| // The same RenderViewHost should be reused. |
| EXPECT_FALSE(pending_rvh()); |
| EXPECT_TRUE(last_rvh == rvh()); |
| test_rvh()->SendNavigate(new_id, kUrl); // The same page_id returned. |
| EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1); |
| EXPECT_FALSE(controller().GetPendingEntry()); |
| // New message should be sent out to make sure to enter view-source mode. |
| EXPECT_TRUE(process()->sink().GetUniqueMessageMatching( |
| ViewMsg_EnableViewSourceMode::ID)); |
| } |
| |
| // Tests the Init function by checking the initial RenderViewHost. |
| TEST_F(RenderViewHostManagerTest, Init) { |
| // Using TestBrowserContext. |
| SiteInstanceImpl* instance = |
| static_cast<SiteInstanceImpl*>(SiteInstance::Create(browser_context())); |
| EXPECT_FALSE(instance->HasSite()); |
| |
| TestWebContents web_contents(browser_context(), instance); |
| RenderViewHostManager manager(&web_contents, &web_contents); |
| |
| manager.Init(browser_context(), instance, MSG_ROUTING_NONE); |
| |
| RenderViewHost* host = manager.current_host(); |
| ASSERT_TRUE(host); |
| EXPECT_TRUE(instance == host->GetSiteInstance()); |
| EXPECT_TRUE(&web_contents == host->GetDelegate()); |
| EXPECT_TRUE(manager.GetRenderWidgetHostView()); |
| EXPECT_FALSE(manager.pending_render_view_host()); |
| } |
| |
| // Tests the Navigate function. We navigate three sites consecutively and check |
| // how the pending/committed RenderViewHost are modified. |
| TEST_F(RenderViewHostManagerTest, Navigate) { |
| TestNotificationTracker notifications; |
| |
| SiteInstance* instance = SiteInstance::Create(browser_context()); |
| |
| TestWebContents web_contents(browser_context(), instance); |
| notifications.ListenFor( |
| content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, |
| content::Source<NavigationController>( |
| &web_contents.GetController())); |
| |
| // Create. |
| RenderViewHostManager manager(&web_contents, &web_contents); |
| |
| manager.Init(browser_context(), instance, MSG_ROUTING_NONE); |
| |
| RenderViewHost* host; |
| |
| // 1) The first navigation. -------------------------- |
| const GURL kUrl1("http://www.google.com/"); |
| NavigationEntryImpl entry1( |
| NULL /* instance */, -1 /* page_id */, kUrl1, content::Referrer(), |
| string16() /* title */, content::PAGE_TRANSITION_TYPED, |
| false /* is_renderer_init */); |
| host = manager.Navigate(entry1); |
| |
| // The RenderViewHost created in Init will be reused. |
| EXPECT_TRUE(host == manager.current_host()); |
| EXPECT_FALSE(manager.pending_render_view_host()); |
| |
| // Commit. |
| manager.DidNavigateMainFrame(host); |
| // Commit to SiteInstance should be delayed until RenderView commit. |
| EXPECT_TRUE(host == manager.current_host()); |
| ASSERT_TRUE(host); |
| EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> |
| HasSite()); |
| static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1); |
| |
| // 2) Navigate to next site. ------------------------- |
| const GURL kUrl2("http://www.google.com/foo"); |
| NavigationEntryImpl entry2( |
| NULL /* instance */, -1 /* page_id */, kUrl2, |
| content::Referrer(kUrl1, WebKit::WebReferrerPolicyDefault), |
| string16() /* title */, content::PAGE_TRANSITION_LINK, |
| true /* is_renderer_init */); |
| host = manager.Navigate(entry2); |
| |
| // The RenderViewHost created in Init will be reused. |
| EXPECT_TRUE(host == manager.current_host()); |
| EXPECT_FALSE(manager.pending_render_view_host()); |
| |
| // Commit. |
| manager.DidNavigateMainFrame(host); |
| EXPECT_TRUE(host == manager.current_host()); |
| ASSERT_TRUE(host); |
| EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> |
| HasSite()); |
| |
| // 3) Cross-site navigate to next site. -------------- |
| const GURL kUrl3("http://webkit.org/"); |
| NavigationEntryImpl entry3( |
| NULL /* instance */, -1 /* page_id */, kUrl3, |
| content::Referrer(kUrl2, WebKit::WebReferrerPolicyDefault), |
| string16() /* title */, content::PAGE_TRANSITION_LINK, |
| false /* is_renderer_init */); |
| host = manager.Navigate(entry3); |
| |
| // A new RenderViewHost should be created. |
| EXPECT_TRUE(manager.pending_render_view_host()); |
| ASSERT_EQ(host, manager.pending_render_view_host()); |
| |
| notifications.Reset(); |
| |
| // Commit. |
| manager.DidNavigateMainFrame(manager.pending_render_view_host()); |
| EXPECT_TRUE(host == manager.current_host()); |
| ASSERT_TRUE(host); |
| EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> |
| HasSite()); |
| // Check the pending RenderViewHost has been committed. |
| EXPECT_FALSE(manager.pending_render_view_host()); |
| |
| // We should observe a notification. |
| EXPECT_TRUE(notifications.Check1AndReset( |
| content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED)); |
| } |
| |
| // Tests the Navigate function. In this unit test we verify that the Navigate |
| // function can handle a new navigation event before the previous navigation |
| // has been committed. This is also a regression test for |
| // http://crbug.com/104600. |
| TEST_F(RenderViewHostManagerTest, NavigateWithEarlyReNavigation) { |
| TestNotificationTracker notifications; |
| |
| SiteInstance* instance = SiteInstance::Create(browser_context()); |
| |
| TestWebContents web_contents(browser_context(), instance); |
| notifications.ListenFor( |
| content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, |
| content::Source<NavigationController>( |
| &web_contents.GetController())); |
| |
| // Create. |
| RenderViewHostManager manager(&web_contents, &web_contents); |
| |
| manager.Init(browser_context(), instance, MSG_ROUTING_NONE); |
| |
| // 1) The first navigation. -------------------------- |
| const GURL kUrl1("http://www.google.com/"); |
| NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1, |
| content::Referrer(), string16() /* title */, |
| content::PAGE_TRANSITION_TYPED, |
| false /* is_renderer_init */); |
| RenderViewHost* host = manager.Navigate(entry1); |
| |
| // The RenderViewHost created in Init will be reused. |
| EXPECT_TRUE(host == manager.current_host()); |
| EXPECT_FALSE(manager.pending_render_view_host()); |
| |
| // We should observe a notification. |
| EXPECT_TRUE(notifications.Check1AndReset( |
| content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED)); |
| notifications.Reset(); |
| |
| // Commit. |
| manager.DidNavigateMainFrame(host); |
| |
| // Commit to SiteInstance should be delayed until RenderView commit. |
| EXPECT_TRUE(host == manager.current_host()); |
| ASSERT_TRUE(host); |
| EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> |
| HasSite()); |
| static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1); |
| |
| // 2) Cross-site navigate to next site. ------------------------- |
| const GURL kUrl2("http://www.example.com"); |
| NavigationEntryImpl entry2( |
| NULL /* instance */, -1 /* page_id */, kUrl2, content::Referrer(), |
| string16() /* title */, content::PAGE_TRANSITION_TYPED, |
| false /* is_renderer_init */); |
| RenderViewHostImpl* host2 = static_cast<RenderViewHostImpl*>( |
| manager.Navigate(entry2)); |
| int host2_process_id = host2->GetProcess()->GetID(); |
| |
| // A new RenderViewHost should be created. |
| EXPECT_TRUE(manager.pending_render_view_host()); |
| ASSERT_EQ(host2, manager.pending_render_view_host()); |
| EXPECT_NE(host2, host); |
| |
| // Check that the navigation is still suspended because the old RVH |
| // is not swapped out, yet. |
| EXPECT_TRUE(host2->are_navigations_suspended()); |
| MockRenderProcessHost* test_process_host2 = |
| static_cast<MockRenderProcessHost*>(host2->GetProcess()); |
| test_process_host2->sink().ClearMessages(); |
| host2->NavigateToURL(kUrl2); |
| EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching( |
| ViewMsg_Navigate::ID)); |
| |
| // Allow closing the current Render View (precondition for swapping out |
| // the RVH): Simulate response from RenderView for ViewMsg_ShouldClose sent by |
| // FirePageBeforeUnload. |
| TestRenderViewHost* test_host = static_cast<TestRenderViewHost*>(host); |
| MockRenderProcessHost* test_process_host = |
| static_cast<MockRenderProcessHost*>(test_host->GetProcess()); |
| EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( |
| ViewMsg_ShouldClose::ID)); |
| test_host->SendShouldCloseACK(true); |
| |
| // CrossSiteResourceHandler::StartCrossSiteTransition triggers a |
| // call of RenderViewHostManager::OnCrossSiteResponse before |
| // RenderViewHostManager::DidNavigateMainFrame is called. |
| // The RVH is not swapped out until the commit. |
| manager.OnCrossSiteResponse(host2->GetProcess()->GetID(), |
| host2->GetPendingRequestId()); |
| EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( |
| ViewMsg_SwapOut::ID)); |
| test_host->OnSwapOutACK(false); |
| |
| EXPECT_EQ(host, manager.current_host()); |
| EXPECT_FALSE(static_cast<RenderViewHostImpl*>( |
| manager.current_host())->is_swapped_out()); |
| EXPECT_EQ(host2, manager.pending_render_view_host()); |
| // There should be still no navigation messages being sent. |
| EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching( |
| ViewMsg_Navigate::ID)); |
| |
| // 3) Cross-site navigate to next site before 2) has committed. -------------- |
| const GURL kUrl3("http://webkit.org/"); |
| NavigationEntryImpl entry3(NULL /* instance */, -1 /* page_id */, kUrl3, |
| content::Referrer(), string16() /* title */, |
| content::PAGE_TRANSITION_TYPED, |
| false /* is_renderer_init */); |
| test_process_host->sink().ClearMessages(); |
| RenderViewHost* host3 = manager.Navigate(entry3); |
| |
| // A new RenderViewHost should be created. host2 is now deleted. |
| EXPECT_TRUE(manager.pending_render_view_host()); |
| ASSERT_EQ(host3, manager.pending_render_view_host()); |
| EXPECT_NE(host3, host); |
| EXPECT_NE(host3->GetProcess()->GetID(), host2_process_id); |
| |
| // Navigations in the new RVH should be suspended, which is ok because the |
| // old RVH is not yet swapped out and can respond to a second beforeunload |
| // request. |
| EXPECT_TRUE(static_cast<RenderViewHostImpl*>( |
| host3)->are_navigations_suspended()); |
| EXPECT_EQ(host, manager.current_host()); |
| EXPECT_FALSE(static_cast<RenderViewHostImpl*>( |
| manager.current_host())->is_swapped_out()); |
| |
| // Simulate a response to the second beforeunload request. |
| EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( |
| ViewMsg_ShouldClose::ID)); |
| test_host->SendShouldCloseACK(true); |
| |
| // CrossSiteResourceHandler::StartCrossSiteTransition triggers a |
| // call of RenderViewHostManager::OnCrossSiteResponse before |
| // RenderViewHostManager::DidNavigateMainFrame is called. |
| // The RVH is not swapped out until the commit. |
| manager.OnCrossSiteResponse(host3->GetProcess()->GetID(), |
| static_cast<RenderViewHostImpl*>( |
| host3)->GetPendingRequestId()); |
| EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( |
| ViewMsg_SwapOut::ID)); |
| test_host->OnSwapOutACK(false); |
| |
| // Commit. |
| manager.DidNavigateMainFrame(host3); |
| EXPECT_TRUE(host3 == manager.current_host()); |
| ASSERT_TRUE(host3); |
| EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host3->GetSiteInstance())-> |
| HasSite()); |
| // Check the pending RenderViewHost has been committed. |
| EXPECT_FALSE(manager.pending_render_view_host()); |
| |
| // We should observe a notification. |
| EXPECT_TRUE(notifications.Check1AndReset( |
| content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED)); |
| } |
| |
| // Tests WebUI creation. |
| TEST_F(RenderViewHostManagerTest, WebUI) { |
| set_should_create_webui(true); |
| BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); |
| SiteInstance* instance = SiteInstance::Create(browser_context()); |
| |
| TestWebContents web_contents(browser_context(), instance); |
| RenderViewHostManager manager(&web_contents, &web_contents); |
| |
| manager.Init(browser_context(), instance, MSG_ROUTING_NONE); |
| |
| const GURL kUrl(chrome::kTestNewTabURL); |
| NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl, |
| content::Referrer(), string16() /* title */, |
| content::PAGE_TRANSITION_TYPED, |
| false /* is_renderer_init */); |
| RenderViewHost* host = manager.Navigate(entry); |
| |
| EXPECT_TRUE(host); |
| EXPECT_TRUE(host == manager.current_host()); |
| EXPECT_FALSE(manager.pending_render_view_host()); |
| |
| // It's important that the site instance get set on the Web UI page as soon |
| // as the navigation starts, rather than lazily after it commits, so we don't |
| // try to re-use the SiteInstance/process for non DOM-UI things that may |
| // get loaded in between. |
| EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> |
| HasSite()); |
| EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSite()); |
| |
| // The Web UI is committed immediately because the RenderViewHost has not been |
| // used yet. UpdateRendererStateForNavigate() took the short cut path. |
| EXPECT_FALSE(manager.pending_web_ui()); |
| EXPECT_TRUE(manager.web_ui()); |
| |
| // Commit. |
| manager.DidNavigateMainFrame(host); |
| } |
| |
| // Tests that we don't end up in an inconsistent state if a page does a back and |
| // then reload. http://crbug.com/51680 |
| TEST_F(RenderViewHostManagerTest, PageDoesBackAndReload) { |
| const GURL kUrl1("http://www.google.com/"); |
| const GURL kUrl2("http://www.evil-site.com/"); |
| |
| // Navigate to a safe site, then an evil site. |
| // This will switch RenderViewHosts. We cannot assert that the first and |
| // second RVHs are different, though, because the first one may be promptly |
| // deleted. |
| contents()->NavigateAndCommit(kUrl1); |
| contents()->NavigateAndCommit(kUrl2); |
| RenderViewHost* evil_rvh = contents()->GetRenderViewHost(); |
| |
| // Now let's simulate the evil page calling history.back(). |
| contents()->OnGoToEntryAtOffset(-1); |
| // We should have a new pending RVH. |
| // Note that in this case, the navigation has not committed, so evil_rvh will |
| // not be deleted yet. |
| EXPECT_NE(evil_rvh, contents()->GetRenderManagerForTesting()-> |
| pending_render_view_host()); |
| |
| // Before that RVH has committed, the evil page reloads itself. |
| ViewHostMsg_FrameNavigate_Params params; |
| params.page_id = 1; |
| params.url = kUrl2; |
| params.transition = content::PAGE_TRANSITION_CLIENT_REDIRECT; |
| params.should_update_history = false; |
| params.gesture = NavigationGestureAuto; |
| params.was_within_same_page = false; |
| params.is_post = false; |
| params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(kUrl2)); |
| contents()->DidNavigate(evil_rvh, params); |
| |
| // That should have cancelled the pending RVH, and the evil RVH should be the |
| // current one. |
| EXPECT_TRUE(contents()->GetRenderManagerForTesting()-> |
| pending_render_view_host() == NULL); |
| EXPECT_EQ(evil_rvh, contents()->GetRenderManagerForTesting()->current_host()); |
| |
| // Also we should not have a pending navigation entry. |
| NavigationEntry* entry = contents()->GetController().GetActiveEntry(); |
| ASSERT_TRUE(entry != NULL); |
| EXPECT_EQ(kUrl2, entry->GetURL()); |
| } |
| |
| // Ensure that we can go back and forward even if a SwapOut ACK isn't received. |
| // See http://crbug.com/93427. |
| TEST_F(RenderViewHostManagerTest, NavigateAfterMissingSwapOutACK) { |
| const GURL kUrl1("http://www.google.com/"); |
| const GURL kUrl2("http://www.chromium.org/"); |
| |
| // Navigate to two pages. |
| contents()->NavigateAndCommit(kUrl1); |
| TestRenderViewHost* rvh1 = test_rvh(); |
| contents()->NavigateAndCommit(kUrl2); |
| TestRenderViewHost* rvh2 = test_rvh(); |
| |
| // Now go back, but suppose the SwapOut_ACK isn't received. This shouldn't |
| // happen, but we have seen it when going back quickly across many entries |
| // (http://crbug.com/93427). |
| contents()->GetController().GoBack(); |
| EXPECT_TRUE(rvh2->is_waiting_for_beforeunload_ack()); |
| contents()->ProceedWithCrossSiteNavigation(); |
| EXPECT_FALSE(rvh2->is_waiting_for_beforeunload_ack()); |
| rvh2->SwapOut(1, 1); |
| EXPECT_TRUE(rvh2->is_waiting_for_unload_ack()); |
| |
| // The back navigation commits. We should proactively clear the |
| // is_waiting_for_unload_ack state to be safe. |
| const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry(); |
| rvh1->SendNavigate(entry1->GetPageID(), entry1->GetURL()); |
| EXPECT_TRUE(rvh2->is_swapped_out()); |
| EXPECT_FALSE(rvh2->is_waiting_for_unload_ack()); |
| |
| // We should be able to navigate forward. |
| contents()->GetController().GoForward(); |
| contents()->ProceedWithCrossSiteNavigation(); |
| const NavigationEntry* entry2 = contents()->GetController().GetPendingEntry(); |
| rvh2->SendNavigate(entry2->GetPageID(), entry2->GetURL()); |
| EXPECT_EQ(rvh2, rvh()); |
| EXPECT_FALSE(rvh2->is_swapped_out()); |
| EXPECT_TRUE(rvh1->is_swapped_out()); |
| } |