OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/utf_string_conversions.h" | |
6 #include "content/browser/browser_thread_impl.h" | |
7 #include "content/browser/mock_content_browser_client.h" | |
8 #include "content/browser/renderer_host/test_render_view_host.h" | |
9 #include "content/browser/site_instance_impl.h" | |
10 #include "content/browser/tab_contents/render_view_host_manager.h" | |
11 #include "content/browser/tab_contents/test_web_contents.h" | |
12 #include "content/browser/web_contents/navigation_entry_impl.h" | |
13 #include "content/browser/web_contents/navigation_controller_impl.h" | |
14 #include "content/common/test_url_constants.h" | |
15 #include "content/common/view_messages.h" | |
16 #include "content/public/browser/notification_details.h" | |
17 #include "content/public/browser/notification_source.h" | |
18 #include "content/public/browser/notification_types.h" | |
19 #include "content/public/browser/web_ui_controller.h" | |
20 #include "content/public/browser/web_ui_controller_factory.h" | |
21 #include "content/public/common/page_transition_types.h" | |
22 #include "content/public/common/url_constants.h" | |
23 #include "content/test/mock_render_process_host.h" | |
24 #include "content/test/test_browser_context.h" | |
25 #include "content/test/test_content_client.h" | |
26 #include "content/test/test_notification_tracker.h" | |
27 #include "googleurl/src/url_util.h" | |
28 #include "testing/gtest/include/gtest/gtest.h" | |
29 #include "ui/base/javascript_message_type.h" | |
30 #include "webkit/glue/webkit_glue.h" | |
31 | |
32 using content::BrowserContext; | |
33 using content::BrowserThread; | |
34 using content::BrowserThreadImpl; | |
35 using content::MockRenderProcessHost; | |
36 using content::NavigationController; | |
37 using content::NavigationEntry; | |
38 using content::NavigationEntryImpl; | |
39 using content::RenderViewHost; | |
40 using content::RenderViewHostImpl; | |
41 using content::RenderViewHostImplTestHarness; | |
42 using content::SiteInstance; | |
43 using content::TestRenderViewHost; | |
44 using content::TestWebContents; | |
45 using content::WebContents; | |
46 using content::WebUI; | |
47 using content::WebUIController; | |
48 | |
49 namespace { | |
50 | |
51 class RenderViewHostManagerTestWebUIControllerFactory | |
52 : public content::WebUIControllerFactory { | |
53 public: | |
54 RenderViewHostManagerTestWebUIControllerFactory() | |
55 : should_create_webui_(false) { | |
56 } | |
57 virtual ~RenderViewHostManagerTestWebUIControllerFactory() {} | |
58 | |
59 void set_should_create_webui(bool should_create_webui) { | |
60 should_create_webui_ = should_create_webui; | |
61 } | |
62 | |
63 // WebUIFactory implementation. | |
64 virtual WebUIController* CreateWebUIControllerForURL( | |
65 WebUI* web_ui, const GURL& url) const OVERRIDE { | |
66 if (!(should_create_webui_ && | |
67 content::GetContentClient()->HasWebUIScheme(url))) | |
68 return NULL; | |
69 return new WebUIController(web_ui); | |
70 } | |
71 | |
72 virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context, | |
73 const GURL& url) const OVERRIDE { | |
74 return WebUI::kNoWebUI; | |
75 } | |
76 | |
77 virtual bool UseWebUIForURL(BrowserContext* browser_context, | |
78 const GURL& url) const OVERRIDE { | |
79 return content::GetContentClient()->HasWebUIScheme(url); | |
80 } | |
81 | |
82 virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context, | |
83 const GURL& url) const OVERRIDE { | |
84 return content::GetContentClient()->HasWebUIScheme(url); | |
85 } | |
86 | |
87 virtual bool IsURLAcceptableForWebUI(BrowserContext* browser_context, | |
88 const GURL& url) const OVERRIDE { | |
89 return false; | |
90 } | |
91 | |
92 private: | |
93 bool should_create_webui_; | |
94 | |
95 DISALLOW_COPY_AND_ASSIGN(RenderViewHostManagerTestWebUIControllerFactory); | |
96 }; | |
97 | |
98 class RenderViewHostManagerTestClient : public TestContentClient { | |
99 public: | |
100 RenderViewHostManagerTestClient() { | |
101 } | |
102 | |
103 virtual bool HasWebUIScheme(const GURL& url) const OVERRIDE { | |
104 return url.SchemeIs(chrome::kChromeUIScheme); | |
105 } | |
106 }; | |
107 | |
108 class RenderViewHostManagerTestBrowserClient | |
109 : public content::MockContentBrowserClient { | |
110 public: | |
111 RenderViewHostManagerTestBrowserClient() {} | |
112 virtual ~RenderViewHostManagerTestBrowserClient() {} | |
113 | |
114 void set_should_create_webui(bool should_create_webui) { | |
115 factory_.set_should_create_webui(should_create_webui); | |
116 } | |
117 | |
118 // content::MockContentBrowserClient implementation. | |
119 virtual content::WebUIControllerFactory* | |
120 GetWebUIControllerFactory() OVERRIDE { | |
121 return &factory_; | |
122 } | |
123 | |
124 private: | |
125 RenderViewHostManagerTestWebUIControllerFactory factory_; | |
126 | |
127 DISALLOW_COPY_AND_ASSIGN(RenderViewHostManagerTestBrowserClient); | |
128 }; | |
129 | |
130 } // namespace | |
131 | |
132 class RenderViewHostManagerTest | |
133 : public RenderViewHostImplTestHarness { | |
134 public: | |
135 virtual void SetUp() OVERRIDE { | |
136 RenderViewHostImplTestHarness::SetUp(); | |
137 old_client_ = content::GetContentClient(); | |
138 old_browser_client_ = content::GetContentClient()->browser(); | |
139 content::SetContentClient(&client_); | |
140 content::GetContentClient()->set_browser(&browser_client_); | |
141 url_util::AddStandardScheme(chrome::kChromeUIScheme); | |
142 } | |
143 | |
144 virtual void TearDown() OVERRIDE { | |
145 RenderViewHostImplTestHarness::TearDown(); | |
146 content::GetContentClient()->set_browser(old_browser_client_); | |
147 content::SetContentClient(old_client_); | |
148 } | |
149 | |
150 void set_should_create_webui(bool should_create_webui) { | |
151 browser_client_.set_should_create_webui(should_create_webui); | |
152 } | |
153 | |
154 void NavigateActiveAndCommit(const GURL& url) { | |
155 // Note: we navigate the active RenderViewHost because previous navigations | |
156 // won't have committed yet, so NavigateAndCommit does the wrong thing | |
157 // for us. | |
158 controller().LoadURL( | |
159 url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string()); | |
160 TestRenderViewHost* old_rvh = test_rvh(); | |
161 | |
162 // Simulate the ShouldClose_ACK that is received from the current renderer | |
163 // for a cross-site navigation. | |
164 if (old_rvh != active_rvh()) | |
165 old_rvh->SendShouldCloseACK(true); | |
166 | |
167 // Commit the navigation with a new page ID. | |
168 int32 max_page_id = contents()->GetMaxPageIDForSiteInstance( | |
169 active_rvh()->GetSiteInstance()); | |
170 active_test_rvh()->SendNavigate(max_page_id + 1, url); | |
171 | |
172 // Simulate the SwapOut_ACK that fires if you commit a cross-site navigation | |
173 // without making any network requests. | |
174 if (old_rvh != active_rvh()) | |
175 old_rvh->OnSwapOutACK(); | |
176 } | |
177 | |
178 bool ShouldSwapProcesses(RenderViewHostManager* manager, | |
179 const NavigationEntryImpl* cur_entry, | |
180 const NavigationEntryImpl* new_entry) const { | |
181 return manager->ShouldSwapProcessesForNavigation(cur_entry, new_entry); | |
182 } | |
183 | |
184 private: | |
185 RenderViewHostManagerTestClient client_; | |
186 RenderViewHostManagerTestBrowserClient browser_client_; | |
187 content::ContentClient* old_client_; | |
188 content::ContentBrowserClient* old_browser_client_; | |
189 }; | |
190 | |
191 // Tests that when you navigate from the New TabPage to another page, and | |
192 // then do that same thing in another tab, that the two resulting pages have | |
193 // different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is | |
194 // a regression test for bug 9364. | |
195 TEST_F(RenderViewHostManagerTest, NewTabPageProcesses) { | |
196 BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); | |
197 const GURL kNtpUrl(chrome::kTestNewTabURL); | |
198 const GURL kDestUrl("http://www.google.com/"); | |
199 | |
200 // Navigate our first tab to the new tab page and then to the destination. | |
201 NavigateActiveAndCommit(kNtpUrl); | |
202 NavigateActiveAndCommit(kDestUrl); | |
203 | |
204 // Make a second tab. | |
205 TestWebContents contents2(browser_context(), NULL); | |
206 | |
207 // Load the two URLs in the second tab. Note that the first navigation creates | |
208 // a RVH that's not pending (since there is no cross-site transition), so | |
209 // we use the committed one. | |
210 contents2.GetController().LoadURL( | |
211 kNtpUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, | |
212 std::string()); | |
213 TestRenderViewHost* ntp_rvh2 = static_cast<TestRenderViewHost*>( | |
214 contents2.GetRenderManagerForTesting()->current_host()); | |
215 EXPECT_FALSE(contents2.cross_navigation_pending()); | |
216 ntp_rvh2->SendNavigate(100, kNtpUrl); | |
217 | |
218 // The second one is the opposite, creating a cross-site transition and | |
219 // requiring a beforeunload ack. | |
220 contents2.GetController().LoadURL( | |
221 kDestUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, | |
222 std::string()); | |
223 EXPECT_TRUE(contents2.cross_navigation_pending()); | |
224 TestRenderViewHost* dest_rvh2 = static_cast<TestRenderViewHost*>( | |
225 contents2.GetRenderManagerForTesting()->pending_render_view_host()); | |
226 ASSERT_TRUE(dest_rvh2); | |
227 ntp_rvh2->SendShouldCloseACK(true); | |
228 dest_rvh2->SendNavigate(101, kDestUrl); | |
229 ntp_rvh2->OnSwapOutACK(); | |
230 | |
231 // The two RVH's should be different in every way. | |
232 EXPECT_NE(active_rvh()->GetProcess(), dest_rvh2->GetProcess()); | |
233 EXPECT_NE(active_rvh()->GetSiteInstance(), dest_rvh2->GetSiteInstance()); | |
234 EXPECT_NE(static_cast<SiteInstanceImpl*>(active_rvh()->GetSiteInstance())-> | |
235 browsing_instance_, | |
236 static_cast<SiteInstanceImpl*>(dest_rvh2->GetSiteInstance())-> | |
237 browsing_instance_); | |
238 | |
239 // Navigate both to the new tab page, and verify that they share a | |
240 // SiteInstance. | |
241 NavigateActiveAndCommit(kNtpUrl); | |
242 | |
243 contents2.GetController().LoadURL( | |
244 kNtpUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, | |
245 std::string()); | |
246 dest_rvh2->SendShouldCloseACK(true); | |
247 static_cast<TestRenderViewHost*>(contents2.GetRenderManagerForTesting()-> | |
248 pending_render_view_host())->SendNavigate(102, kNtpUrl); | |
249 dest_rvh2->OnSwapOutACK(); | |
250 | |
251 EXPECT_EQ(active_rvh()->GetSiteInstance(), | |
252 contents2.GetRenderViewHost()->GetSiteInstance()); | |
253 } | |
254 | |
255 // Ensure that the browser ignores most IPC messages that arrive from a | |
256 // RenderViewHost that has been swapped out. We do not want to take | |
257 // action on requests from a non-active renderer. The main exception is | |
258 // for synchronous messages, which cannot be ignored without leaving the | |
259 // renderer in a stuck state. See http://crbug.com/93427. | |
260 TEST_F(RenderViewHostManagerTest, FilterMessagesWhileSwappedOut) { | |
261 BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); | |
262 const GURL kNtpUrl(chrome::kTestNewTabURL); | |
263 const GURL kDestUrl("http://www.google.com/"); | |
264 | |
265 // Navigate our first tab to the new tab page and then to the destination. | |
266 NavigateActiveAndCommit(kNtpUrl); | |
267 TestRenderViewHost* ntp_rvh = static_cast<TestRenderViewHost*>( | |
268 contents()->GetRenderManagerForTesting()->current_host()); | |
269 | |
270 // Send an update title message and make sure it works. | |
271 const string16 ntp_title = ASCIIToUTF16("NTP Title"); | |
272 WebKit::WebTextDirection direction = WebKit::WebTextDirectionLeftToRight; | |
273 EXPECT_TRUE(ntp_rvh->OnMessageReceived( | |
274 ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction))); | |
275 EXPECT_EQ(ntp_title, contents()->GetTitle()); | |
276 | |
277 // Navigate to a cross-site URL. | |
278 contents()->GetController().LoadURL( | |
279 kDestUrl, content::Referrer(), content::PAGE_TRANSITION_LINK, | |
280 std::string()); | |
281 EXPECT_TRUE(contents()->cross_navigation_pending()); | |
282 TestRenderViewHost* dest_rvh = static_cast<TestRenderViewHost*>( | |
283 contents()->GetRenderManagerForTesting()->pending_render_view_host()); | |
284 ASSERT_TRUE(dest_rvh); | |
285 EXPECT_NE(ntp_rvh, dest_rvh); | |
286 | |
287 // BeforeUnload finishes. | |
288 ntp_rvh->SendShouldCloseACK(true); | |
289 | |
290 // Assume SwapOutACK times out, so the dest_rvh proceeds and commits. | |
291 dest_rvh->SendNavigate(101, kDestUrl); | |
292 | |
293 // The new RVH should be able to update its title. | |
294 const string16 dest_title = ASCIIToUTF16("Google"); | |
295 EXPECT_TRUE(dest_rvh->OnMessageReceived( | |
296 ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 101, dest_title, | |
297 direction))); | |
298 EXPECT_EQ(dest_title, contents()->GetTitle()); | |
299 | |
300 // The old renderer, being slow, now updates the title. It should be filtered | |
301 // out and not take effect. | |
302 EXPECT_TRUE(ntp_rvh->is_swapped_out()); | |
303 EXPECT_TRUE(ntp_rvh->OnMessageReceived( | |
304 ViewHostMsg_UpdateTitle(rvh()->GetRoutingID(), 0, ntp_title, direction))); | |
305 EXPECT_EQ(dest_title, contents()->GetTitle()); | |
306 | |
307 // We cannot filter out synchronous IPC messages, because the renderer would | |
308 // be left waiting for a reply. We pick RunBeforeUnloadConfirm as an example | |
309 // that can run easily within a unit test, and that needs to receive a reply | |
310 // without showing an actual dialog. | |
311 MockRenderProcessHost* ntp_process_host = | |
312 static_cast<MockRenderProcessHost*>(ntp_rvh->GetProcess()); | |
313 ntp_process_host->sink().ClearMessages(); | |
314 const string16 msg = ASCIIToUTF16("Message"); | |
315 bool result = false; | |
316 string16 unused; | |
317 ViewHostMsg_RunBeforeUnloadConfirm before_unload_msg( | |
318 rvh()->GetRoutingID(), kNtpUrl, msg, false, &result, &unused); | |
319 // Enable pumping for check in BrowserMessageFilter::CheckCanDispatchOnUI. | |
320 before_unload_msg.EnableMessagePumping(); | |
321 EXPECT_TRUE(ntp_rvh->OnMessageReceived(before_unload_msg)); | |
322 EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID)); | |
323 | |
324 // Also test RunJavaScriptMessage. | |
325 ntp_process_host->sink().ClearMessages(); | |
326 ViewHostMsg_RunJavaScriptMessage js_msg( | |
327 rvh()->GetRoutingID(), msg, msg, kNtpUrl, | |
328 ui::JAVASCRIPT_MESSAGE_TYPE_CONFIRM, &result, &unused); | |
329 js_msg.EnableMessagePumping(); | |
330 EXPECT_TRUE(ntp_rvh->OnMessageReceived(js_msg)); | |
331 EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID)); | |
332 } | |
333 | |
334 // When there is an error with the specified page, renderer exits view-source | |
335 // mode. See WebFrameImpl::DidFail(). We check by this test that | |
336 // EnableViewSourceMode message is sent on every navigation regardless | |
337 // RenderView is being newly created or reused. | |
338 TEST_F(RenderViewHostManagerTest, AlwaysSendEnableViewSourceMode) { | |
339 BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); | |
340 const GURL kNtpUrl(chrome::kTestNewTabURL); | |
341 const GURL kUrl("view-source:http://foo"); | |
342 | |
343 // We have to navigate to some page at first since without this, the first | |
344 // navigation will reuse the SiteInstance created by Init(), and the second | |
345 // one will create a new SiteInstance. Because current_instance and | |
346 // new_instance will be different, a new RenderViewHost will be created for | |
347 // the second navigation. We have to avoid this in order to exercise the | |
348 // target code patch. | |
349 NavigateActiveAndCommit(kNtpUrl); | |
350 | |
351 // Navigate. | |
352 controller().LoadURL( | |
353 kUrl, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string()); | |
354 // Simulate response from RenderView for FirePageBeforeUnload. | |
355 test_rvh()->OnMessageReceived(ViewHostMsg_ShouldClose_ACK( | |
356 rvh()->GetRoutingID(), true, base::TimeTicks(), base::TimeTicks())); | |
357 ASSERT_TRUE(pending_rvh()); // New pending RenderViewHost will be created. | |
358 RenderViewHost* last_rvh = pending_rvh(); | |
359 int32 new_id = contents()->GetMaxPageIDForSiteInstance( | |
360 active_rvh()->GetSiteInstance()) + 1; | |
361 pending_test_rvh()->SendNavigate(new_id, kUrl); | |
362 EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1); | |
363 ASSERT_TRUE(controller().GetLastCommittedEntry()); | |
364 EXPECT_TRUE(kUrl == controller().GetLastCommittedEntry()->GetURL()); | |
365 EXPECT_FALSE(controller().GetPendingEntry()); | |
366 // Because we're using TestWebContents and TestRenderViewHost in this | |
367 // unittest, no one calls TabContents::RenderViewCreated(). So, we see no | |
368 // EnableViewSourceMode message, here. | |
369 | |
370 // Clear queued messages before load. | |
371 process()->sink().ClearMessages(); | |
372 // Navigate, again. | |
373 controller().LoadURL( | |
374 kUrl, content::Referrer(), content::PAGE_TRANSITION_TYPED, std::string()); | |
375 // The same RenderViewHost should be reused. | |
376 EXPECT_FALSE(pending_rvh()); | |
377 EXPECT_TRUE(last_rvh == rvh()); | |
378 test_rvh()->SendNavigate(new_id, kUrl); // The same page_id returned. | |
379 EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1); | |
380 EXPECT_FALSE(controller().GetPendingEntry()); | |
381 // New message should be sent out to make sure to enter view-source mode. | |
382 EXPECT_TRUE(process()->sink().GetUniqueMessageMatching( | |
383 ViewMsg_EnableViewSourceMode::ID)); | |
384 } | |
385 | |
386 // Tests the Init function by checking the initial RenderViewHost. | |
387 TEST_F(RenderViewHostManagerTest, Init) { | |
388 // Using TestBrowserContext. | |
389 SiteInstanceImpl* instance = | |
390 static_cast<SiteInstanceImpl*>(SiteInstance::Create(browser_context())); | |
391 EXPECT_FALSE(instance->HasSite()); | |
392 | |
393 TestWebContents web_contents(browser_context(), instance); | |
394 RenderViewHostManager manager(&web_contents, &web_contents); | |
395 | |
396 manager.Init(browser_context(), instance, MSG_ROUTING_NONE); | |
397 | |
398 RenderViewHost* host = manager.current_host(); | |
399 ASSERT_TRUE(host); | |
400 EXPECT_TRUE(instance == host->GetSiteInstance()); | |
401 EXPECT_TRUE(&web_contents == host->GetDelegate()); | |
402 EXPECT_TRUE(manager.GetRenderWidgetHostView()); | |
403 EXPECT_FALSE(manager.pending_render_view_host()); | |
404 } | |
405 | |
406 // Tests the Navigate function. We navigate three sites consecutively and check | |
407 // how the pending/committed RenderViewHost are modified. | |
408 TEST_F(RenderViewHostManagerTest, Navigate) { | |
409 TestNotificationTracker notifications; | |
410 | |
411 SiteInstance* instance = SiteInstance::Create(browser_context()); | |
412 | |
413 TestWebContents web_contents(browser_context(), instance); | |
414 notifications.ListenFor( | |
415 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, | |
416 content::Source<NavigationController>( | |
417 &web_contents.GetController())); | |
418 | |
419 // Create. | |
420 RenderViewHostManager manager(&web_contents, &web_contents); | |
421 | |
422 manager.Init(browser_context(), instance, MSG_ROUTING_NONE); | |
423 | |
424 RenderViewHost* host; | |
425 | |
426 // 1) The first navigation. -------------------------- | |
427 const GURL kUrl1("http://www.google.com/"); | |
428 NavigationEntryImpl entry1( | |
429 NULL /* instance */, -1 /* page_id */, kUrl1, content::Referrer(), | |
430 string16() /* title */, content::PAGE_TRANSITION_TYPED, | |
431 false /* is_renderer_init */); | |
432 host = manager.Navigate(entry1); | |
433 | |
434 // The RenderViewHost created in Init will be reused. | |
435 EXPECT_TRUE(host == manager.current_host()); | |
436 EXPECT_FALSE(manager.pending_render_view_host()); | |
437 | |
438 // Commit. | |
439 manager.DidNavigateMainFrame(host); | |
440 // Commit to SiteInstance should be delayed until RenderView commit. | |
441 EXPECT_TRUE(host == manager.current_host()); | |
442 ASSERT_TRUE(host); | |
443 EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> | |
444 HasSite()); | |
445 static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1); | |
446 | |
447 // 2) Navigate to next site. ------------------------- | |
448 const GURL kUrl2("http://www.google.com/foo"); | |
449 NavigationEntryImpl entry2( | |
450 NULL /* instance */, -1 /* page_id */, kUrl2, | |
451 content::Referrer(kUrl1, WebKit::WebReferrerPolicyDefault), | |
452 string16() /* title */, content::PAGE_TRANSITION_LINK, | |
453 true /* is_renderer_init */); | |
454 host = manager.Navigate(entry2); | |
455 | |
456 // The RenderViewHost created in Init will be reused. | |
457 EXPECT_TRUE(host == manager.current_host()); | |
458 EXPECT_FALSE(manager.pending_render_view_host()); | |
459 | |
460 // Commit. | |
461 manager.DidNavigateMainFrame(host); | |
462 EXPECT_TRUE(host == manager.current_host()); | |
463 ASSERT_TRUE(host); | |
464 EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> | |
465 HasSite()); | |
466 | |
467 // 3) Cross-site navigate to next site. -------------- | |
468 const GURL kUrl3("http://webkit.org/"); | |
469 NavigationEntryImpl entry3( | |
470 NULL /* instance */, -1 /* page_id */, kUrl3, | |
471 content::Referrer(kUrl2, WebKit::WebReferrerPolicyDefault), | |
472 string16() /* title */, content::PAGE_TRANSITION_LINK, | |
473 false /* is_renderer_init */); | |
474 host = manager.Navigate(entry3); | |
475 | |
476 // A new RenderViewHost should be created. | |
477 EXPECT_TRUE(manager.pending_render_view_host()); | |
478 ASSERT_EQ(host, manager.pending_render_view_host()); | |
479 | |
480 notifications.Reset(); | |
481 | |
482 // Commit. | |
483 manager.DidNavigateMainFrame(manager.pending_render_view_host()); | |
484 EXPECT_TRUE(host == manager.current_host()); | |
485 ASSERT_TRUE(host); | |
486 EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> | |
487 HasSite()); | |
488 // Check the pending RenderViewHost has been committed. | |
489 EXPECT_FALSE(manager.pending_render_view_host()); | |
490 | |
491 // We should observe a notification. | |
492 EXPECT_TRUE(notifications.Check1AndReset( | |
493 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED)); | |
494 } | |
495 | |
496 // Tests the Navigate function. In this unit test we verify that the Navigate | |
497 // function can handle a new navigation event before the previous navigation | |
498 // has been committed. This is also a regression test for | |
499 // http://crbug.com/104600. | |
500 TEST_F(RenderViewHostManagerTest, NavigateWithEarlyReNavigation) { | |
501 TestNotificationTracker notifications; | |
502 | |
503 SiteInstance* instance = SiteInstance::Create(browser_context()); | |
504 | |
505 TestWebContents web_contents(browser_context(), instance); | |
506 notifications.ListenFor( | |
507 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, | |
508 content::Source<NavigationController>( | |
509 &web_contents.GetController())); | |
510 | |
511 // Create. | |
512 RenderViewHostManager manager(&web_contents, &web_contents); | |
513 | |
514 manager.Init(browser_context(), instance, MSG_ROUTING_NONE); | |
515 | |
516 // 1) The first navigation. -------------------------- | |
517 const GURL kUrl1("http://www.google.com/"); | |
518 NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1, | |
519 content::Referrer(), string16() /* title */, | |
520 content::PAGE_TRANSITION_TYPED, | |
521 false /* is_renderer_init */); | |
522 RenderViewHost* host = manager.Navigate(entry1); | |
523 | |
524 // The RenderViewHost created in Init will be reused. | |
525 EXPECT_TRUE(host == manager.current_host()); | |
526 EXPECT_FALSE(manager.pending_render_view_host()); | |
527 | |
528 // We should observe a notification. | |
529 EXPECT_TRUE(notifications.Check1AndReset( | |
530 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED)); | |
531 notifications.Reset(); | |
532 | |
533 // Commit. | |
534 manager.DidNavigateMainFrame(host); | |
535 | |
536 // Commit to SiteInstance should be delayed until RenderView commit. | |
537 EXPECT_TRUE(host == manager.current_host()); | |
538 ASSERT_TRUE(host); | |
539 EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> | |
540 HasSite()); | |
541 static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1); | |
542 | |
543 // 2) Cross-site navigate to next site. ------------------------- | |
544 const GURL kUrl2("http://www.example.com"); | |
545 NavigationEntryImpl entry2( | |
546 NULL /* instance */, -1 /* page_id */, kUrl2, content::Referrer(), | |
547 string16() /* title */, content::PAGE_TRANSITION_TYPED, | |
548 false /* is_renderer_init */); | |
549 RenderViewHostImpl* host2 = static_cast<RenderViewHostImpl*>( | |
550 manager.Navigate(entry2)); | |
551 int host2_process_id = host2->GetProcess()->GetID(); | |
552 | |
553 // A new RenderViewHost should be created. | |
554 EXPECT_TRUE(manager.pending_render_view_host()); | |
555 ASSERT_EQ(host2, manager.pending_render_view_host()); | |
556 EXPECT_NE(host2, host); | |
557 | |
558 // Check that the navigation is still suspended because the old RVH | |
559 // is not swapped out, yet. | |
560 EXPECT_TRUE(host2->are_navigations_suspended()); | |
561 MockRenderProcessHost* test_process_host2 = | |
562 static_cast<MockRenderProcessHost*>(host2->GetProcess()); | |
563 test_process_host2->sink().ClearMessages(); | |
564 host2->NavigateToURL(kUrl2); | |
565 EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching( | |
566 ViewMsg_Navigate::ID)); | |
567 | |
568 // Allow closing the current Render View (precondition for swapping out | |
569 // the RVH): Simulate response from RenderView for ViewMsg_ShouldClose sent by | |
570 // FirePageBeforeUnload. | |
571 TestRenderViewHost* test_host = static_cast<TestRenderViewHost*>(host); | |
572 MockRenderProcessHost* test_process_host = | |
573 static_cast<MockRenderProcessHost*>(test_host->GetProcess()); | |
574 EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( | |
575 ViewMsg_ShouldClose::ID)); | |
576 test_host->SendShouldCloseACK(true); | |
577 | |
578 // CrossSiteResourceHandler::StartCrossSiteTransition triggers a | |
579 // call of RenderViewHostManager::OnCrossSiteResponse before | |
580 // RenderViewHostManager::DidNavigateMainFrame is called. | |
581 // The RVH is not swapped out until the commit. | |
582 manager.OnCrossSiteResponse(host2->GetProcess()->GetID(), | |
583 host2->GetPendingRequestId()); | |
584 EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( | |
585 ViewMsg_SwapOut::ID)); | |
586 test_host->OnSwapOutACK(); | |
587 | |
588 EXPECT_EQ(host, manager.current_host()); | |
589 EXPECT_FALSE(static_cast<RenderViewHostImpl*>( | |
590 manager.current_host())->is_swapped_out()); | |
591 EXPECT_EQ(host2, manager.pending_render_view_host()); | |
592 // There should be still no navigation messages being sent. | |
593 EXPECT_FALSE(test_process_host2->sink().GetUniqueMessageMatching( | |
594 ViewMsg_Navigate::ID)); | |
595 | |
596 // 3) Cross-site navigate to next site before 2) has committed. -------------- | |
597 const GURL kUrl3("http://webkit.org/"); | |
598 NavigationEntryImpl entry3(NULL /* instance */, -1 /* page_id */, kUrl3, | |
599 content::Referrer(), string16() /* title */, | |
600 content::PAGE_TRANSITION_TYPED, | |
601 false /* is_renderer_init */); | |
602 test_process_host->sink().ClearMessages(); | |
603 RenderViewHost* host3 = manager.Navigate(entry3); | |
604 | |
605 // A new RenderViewHost should be created. host2 is now deleted. | |
606 EXPECT_TRUE(manager.pending_render_view_host()); | |
607 ASSERT_EQ(host3, manager.pending_render_view_host()); | |
608 EXPECT_NE(host3, host); | |
609 EXPECT_NE(host3->GetProcess()->GetID(), host2_process_id); | |
610 | |
611 // Navigations in the new RVH should be suspended, which is ok because the | |
612 // old RVH is not yet swapped out and can respond to a second beforeunload | |
613 // request. | |
614 EXPECT_TRUE(static_cast<RenderViewHostImpl*>( | |
615 host3)->are_navigations_suspended()); | |
616 EXPECT_EQ(host, manager.current_host()); | |
617 EXPECT_FALSE(static_cast<RenderViewHostImpl*>( | |
618 manager.current_host())->is_swapped_out()); | |
619 | |
620 // Simulate a response to the second beforeunload request. | |
621 EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( | |
622 ViewMsg_ShouldClose::ID)); | |
623 test_host->SendShouldCloseACK(true); | |
624 | |
625 // CrossSiteResourceHandler::StartCrossSiteTransition triggers a | |
626 // call of RenderViewHostManager::OnCrossSiteResponse before | |
627 // RenderViewHostManager::DidNavigateMainFrame is called. | |
628 // The RVH is not swapped out until the commit. | |
629 manager.OnCrossSiteResponse(host3->GetProcess()->GetID(), | |
630 static_cast<RenderViewHostImpl*>( | |
631 host3)->GetPendingRequestId()); | |
632 EXPECT_TRUE(test_process_host->sink().GetUniqueMessageMatching( | |
633 ViewMsg_SwapOut::ID)); | |
634 test_host->OnSwapOutACK(); | |
635 | |
636 // Commit. | |
637 manager.DidNavigateMainFrame(host3); | |
638 EXPECT_TRUE(host3 == manager.current_host()); | |
639 ASSERT_TRUE(host3); | |
640 EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host3->GetSiteInstance())-> | |
641 HasSite()); | |
642 // Check the pending RenderViewHost has been committed. | |
643 EXPECT_FALSE(manager.pending_render_view_host()); | |
644 | |
645 // We should observe a notification. | |
646 EXPECT_TRUE(notifications.Check1AndReset( | |
647 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED)); | |
648 } | |
649 | |
650 // Tests WebUI creation. | |
651 TEST_F(RenderViewHostManagerTest, WebUI) { | |
652 set_should_create_webui(true); | |
653 BrowserThreadImpl ui_thread(BrowserThread::UI, MessageLoop::current()); | |
654 SiteInstance* instance = SiteInstance::Create(browser_context()); | |
655 | |
656 TestWebContents web_contents(browser_context(), instance); | |
657 RenderViewHostManager manager(&web_contents, &web_contents); | |
658 | |
659 manager.Init(browser_context(), instance, MSG_ROUTING_NONE); | |
660 | |
661 const GURL kUrl(chrome::kTestNewTabURL); | |
662 NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl, | |
663 content::Referrer(), string16() /* title */, | |
664 content::PAGE_TRANSITION_TYPED, | |
665 false /* is_renderer_init */); | |
666 RenderViewHost* host = manager.Navigate(entry); | |
667 | |
668 EXPECT_TRUE(host); | |
669 EXPECT_TRUE(host == manager.current_host()); | |
670 EXPECT_FALSE(manager.pending_render_view_host()); | |
671 | |
672 // It's important that the site instance get set on the Web UI page as soon | |
673 // as the navigation starts, rather than lazily after it commits, so we don't | |
674 // try to re-use the SiteInstance/process for non DOM-UI things that may | |
675 // get loaded in between. | |
676 EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())-> | |
677 HasSite()); | |
678 EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSite()); | |
679 | |
680 // The Web UI is committed immediately because the RenderViewHost has not been | |
681 // used yet. UpdateRendererStateForNavigate() took the short cut path. | |
682 EXPECT_FALSE(manager.pending_web_ui()); | |
683 EXPECT_TRUE(manager.web_ui()); | |
684 | |
685 // Commit. | |
686 manager.DidNavigateMainFrame(host); | |
687 } | |
688 | |
689 // Tests that we don't end up in an inconsistent state if a page does a back and | |
690 // then reload. http://crbug.com/51680 | |
691 TEST_F(RenderViewHostManagerTest, PageDoesBackAndReload) { | |
692 const GURL kUrl1("http://www.google.com/"); | |
693 const GURL kUrl2("http://www.evil-site.com/"); | |
694 | |
695 // Navigate to a safe site, then an evil site. | |
696 // This will switch RenderViewHosts. We cannot assert that the first and | |
697 // second RVHs are different, though, because the first one may be promptly | |
698 // deleted. | |
699 contents()->NavigateAndCommit(kUrl1); | |
700 contents()->NavigateAndCommit(kUrl2); | |
701 RenderViewHost* evil_rvh = contents()->GetRenderViewHost(); | |
702 | |
703 // Now let's simulate the evil page calling history.back(). | |
704 contents()->OnGoToEntryAtOffset(-1); | |
705 // We should have a new pending RVH. | |
706 // Note that in this case, the navigation has not committed, so evil_rvh will | |
707 // not be deleted yet. | |
708 EXPECT_NE(evil_rvh, contents()->GetRenderManagerForTesting()-> | |
709 pending_render_view_host()); | |
710 | |
711 // Before that RVH has committed, the evil page reloads itself. | |
712 ViewHostMsg_FrameNavigate_Params params; | |
713 params.page_id = 1; | |
714 params.url = kUrl2; | |
715 params.transition = content::PAGE_TRANSITION_CLIENT_REDIRECT; | |
716 params.should_update_history = false; | |
717 params.gesture = NavigationGestureAuto; | |
718 params.was_within_same_page = false; | |
719 params.is_post = false; | |
720 params.content_state = webkit_glue::CreateHistoryStateForURL(GURL(kUrl2)); | |
721 contents()->DidNavigate(evil_rvh, params); | |
722 | |
723 // That should have cancelled the pending RVH, and the evil RVH should be the | |
724 // current one. | |
725 EXPECT_TRUE(contents()->GetRenderManagerForTesting()-> | |
726 pending_render_view_host() == NULL); | |
727 EXPECT_EQ(evil_rvh, contents()->GetRenderManagerForTesting()->current_host()); | |
728 | |
729 // Also we should not have a pending navigation entry. | |
730 NavigationEntry* entry = contents()->GetController().GetActiveEntry(); | |
731 ASSERT_TRUE(entry != NULL); | |
732 EXPECT_EQ(kUrl2, entry->GetURL()); | |
733 } | |
734 | |
735 // Ensure that we can go back and forward even if a SwapOut ACK isn't received. | |
736 // See http://crbug.com/93427. | |
737 TEST_F(RenderViewHostManagerTest, NavigateAfterMissingSwapOutACK) { | |
738 const GURL kUrl1("http://www.google.com/"); | |
739 const GURL kUrl2("http://www.chromium.org/"); | |
740 | |
741 // Navigate to two pages. | |
742 contents()->NavigateAndCommit(kUrl1); | |
743 TestRenderViewHost* rvh1 = test_rvh(); | |
744 contents()->NavigateAndCommit(kUrl2); | |
745 TestRenderViewHost* rvh2 = test_rvh(); | |
746 | |
747 // Now go back, but suppose the SwapOut_ACK isn't received. This shouldn't | |
748 // happen, but we have seen it when going back quickly across many entries | |
749 // (http://crbug.com/93427). | |
750 contents()->GetController().GoBack(); | |
751 EXPECT_TRUE(rvh2->is_waiting_for_beforeunload_ack()); | |
752 contents()->ProceedWithCrossSiteNavigation(); | |
753 EXPECT_FALSE(rvh2->is_waiting_for_beforeunload_ack()); | |
754 rvh2->SwapOut(1, 1); | |
755 EXPECT_TRUE(rvh2->is_waiting_for_unload_ack()); | |
756 | |
757 // The back navigation commits. We should proactively clear the | |
758 // is_waiting_for_unload_ack state to be safe. | |
759 const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry(); | |
760 rvh1->SendNavigate(entry1->GetPageID(), entry1->GetURL()); | |
761 EXPECT_TRUE(rvh2->is_swapped_out()); | |
762 EXPECT_FALSE(rvh2->is_waiting_for_unload_ack()); | |
763 | |
764 // We should be able to navigate forward. | |
765 contents()->GetController().GoForward(); | |
766 contents()->ProceedWithCrossSiteNavigation(); | |
767 const NavigationEntry* entry2 = contents()->GetController().GetPendingEntry(); | |
768 rvh2->SendNavigate(entry2->GetPageID(), entry2->GetURL()); | |
769 EXPECT_EQ(rvh2, rvh()); | |
770 EXPECT_FALSE(rvh2->is_swapped_out()); | |
771 EXPECT_TRUE(rvh1->is_swapped_out()); | |
772 } | |
OLD | NEW |