render_process_host_chrome_browsertest.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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/command_line.h" 6#include "chrome/browser/chrome_notification_types.h" 7#include "chrome/browser/devtools/devtools_window.h" 8#include "chrome/browser/search/search.h" 9#include "chrome/browser/ui/browser.h" 10#include "chrome/browser/ui/browser_commands.h" 11#include "chrome/browser/ui/singleton_tabs.h" 12#include "chrome/browser/ui/tabs/tab_strip_model.h" 13#include "chrome/common/chrome_switches.h" 14#include "chrome/common/url_constants.h" 15#include "chrome/test/base/in_process_browser_test.h" 16#include "chrome/test/base/test_switches.h" 17#include "chrome/test/base/ui_test_utils.h" 18#include "content/public/browser/notification_service.h" 19#include "content/public/browser/render_process_host.h" 20#include "content/public/browser/render_view_host.h" 21#include "content/public/browser/render_widget_host_iterator.h" 22#include "content/public/browser/web_contents.h" 23#include "content/public/browser/web_contents_observer.h" 24#include "content/public/test/browser_test_utils.h" 25 26using content::RenderViewHost; 27using content::RenderWidgetHost; 28using content::WebContents; 29 30namespace { 31 32int RenderProcessHostCount() { 33 content::RenderProcessHost::iterator hosts = 34 content::RenderProcessHost::AllHostsIterator(); 35 int count = 0; 36 while (!hosts.IsAtEnd()) { 37 if (hosts.GetCurrentValue()->HasConnection()) 38 count++; 39 hosts.Advance(); 40 } 41 return count; 42} 43 44RenderViewHost* FindFirstDevToolsHost() { 45 scoped_ptr<content::RenderWidgetHostIterator> widgets( 46 RenderWidgetHost::GetRenderWidgetHosts()); 47 while (content::RenderWidgetHost* widget = widgets->GetNextHost()) { 48 if (!widget->GetProcess()->HasConnection()) 49 continue; 50 if (!widget->IsRenderView()) 51 continue; 52 RenderViewHost* host = RenderViewHost::From(widget); 53 WebContents* contents = WebContents::FromRenderViewHost(host); 54 GURL url = contents->GetURL(); 55 if (url.SchemeIs(content::kChromeDevToolsScheme)) 56 return host; 57 } 58 return NULL; 59} 60 61} // namespace 62 63class ChromeRenderProcessHostTest : public InProcessBrowserTest { 64 public: 65 ChromeRenderProcessHostTest() {} 66 67 // Show a tab, activating the current one if there is one, and wait for 68 // the renderer process to be created or foregrounded, returning the process 69 // handle. 70 base::ProcessHandle ShowSingletonTab(const GURL& page) { 71 chrome::ShowSingletonTab(browser(), page); 72 WebContents* wc = browser()->tab_strip_model()->GetActiveWebContents(); 73 CHECK(wc->GetURL() == page); 74 75 WaitForLauncherThread(); 76 WaitForMessageProcessing(wc); 77 return wc->GetRenderProcessHost()->GetHandle(); 78 } 79 80 // Loads the given url in a new background tab and returns the handle of its 81 // renderer. 82 base::ProcessHandle OpenBackgroundTab(const GURL& page) { 83 ui_test_utils::NavigateToURLWithDisposition(browser(), page, 84 NEW_BACKGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); 85 86 TabStripModel* tab_strip = browser()->tab_strip_model(); 87 WebContents* wc = tab_strip->GetWebContentsAt( 88 tab_strip->active_index() + 1); 89 CHECK(wc->GetVisibleURL() == page); 90 91 WaitForLauncherThread(); 92 WaitForMessageProcessing(wc); 93 return wc->GetRenderProcessHost()->GetHandle(); 94 } 95 96 // Ensures that the backgrounding / foregrounding gets a chance to run. 97 void WaitForLauncherThread() { 98 content::BrowserThread::PostTaskAndReply( 99 content::BrowserThread::PROCESS_LAUNCHER, FROM_HERE, 100 base::Bind(&base::DoNothing), base::MessageLoop::QuitClosure()); 101 base::MessageLoop::current()->Run(); 102 } 103 104 // Implicitly waits for the renderer process associated with the specified 105 // WebContents to process outstanding IPC messages by running some JavaScript 106 // and waiting for the result. 107 void WaitForMessageProcessing(WebContents* wc) { 108 bool result = false; 109 ASSERT_TRUE(content::ExecuteScriptAndExtractBool( 110 wc, "window.domAutomationController.send(true);", &result)); 111 ASSERT_TRUE(result); 112 } 113 114 // When we hit the max number of renderers, verify that the way we do process 115 // sharing behaves correctly. In particular, this test is verifying that even 116 // when we hit the max process limit, that renderers of each type will wind up 117 // in a process of that type, even if that means creating a new process. 118 void TestProcessOverflow() { 119 int tab_count = 1; 120 int host_count = 1; 121 WebContents* tab1 = NULL; 122 WebContents* tab2 = NULL; 123 content::RenderProcessHost* rph1 = NULL; 124 content::RenderProcessHost* rph2 = NULL; 125 content::RenderProcessHost* rph3 = NULL; 126 127 // Change the first tab to be the omnibox page (TYPE_WEBUI). 128 GURL omnibox(chrome::kChromeUIOmniboxURL); 129 ui_test_utils::NavigateToURL(browser(), omnibox); 130 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 131 tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1); 132 rph1 = tab1->GetRenderProcessHost(); 133 EXPECT_EQ(omnibox, tab1->GetURL()); 134 EXPECT_EQ(host_count, RenderProcessHostCount()); 135 136 // Create a new TYPE_TABBED tab. It should be in its own process. 137 GURL page1("data:text/html,hello world1"); 138 139 ui_test_utils::WindowedTabAddedNotificationObserver observer1( 140 content::NotificationService::AllSources()); 141 chrome::ShowSingletonTab(browser(), page1); 142 observer1.Wait(); 143 144 tab_count++; 145 host_count++; 146 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 147 tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1); 148 rph2 = tab1->GetRenderProcessHost(); 149 EXPECT_EQ(tab1->GetURL(), page1); 150 EXPECT_EQ(host_count, RenderProcessHostCount()); 151 EXPECT_NE(rph1, rph2); 152 153 // Create another TYPE_TABBED tab. It should share the previous process. 154 GURL page2("data:text/html,hello world2"); 155 ui_test_utils::WindowedTabAddedNotificationObserver observer2( 156 content::NotificationService::AllSources()); 157 chrome::ShowSingletonTab(browser(), page2); 158 observer2.Wait(); 159 tab_count++; 160 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 161 tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1); 162 EXPECT_EQ(tab2->GetURL(), page2); 163 EXPECT_EQ(host_count, RenderProcessHostCount()); 164 EXPECT_EQ(tab2->GetRenderProcessHost(), rph2); 165 166 // Create another TYPE_WEBUI tab. It should share the process with omnibox. 167 // Note: intentionally create this tab after the TYPE_TABBED tabs to 168 // exercise bug 43448 where extension and WebUI tabs could get combined into 169 // normal renderers. 170 GURL history(chrome::kChromeUIHistoryURL); 171 ui_test_utils::WindowedTabAddedNotificationObserver observer3( 172 content::NotificationService::AllSources()); 173 chrome::ShowSingletonTab(browser(), history); 174 observer3.Wait(); 175 tab_count++; 176 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 177 tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1); 178 EXPECT_EQ(tab2->GetURL(), GURL(history)); 179 EXPECT_EQ(host_count, RenderProcessHostCount()); 180 EXPECT_EQ(tab2->GetRenderProcessHost(), rph1); 181 182 // Create a TYPE_EXTENSION tab. It should be in its own process. 183 // (the bookmark manager is implemented as an extension) 184 GURL bookmarks(chrome::kChromeUIBookmarksURL); 185 ui_test_utils::WindowedTabAddedNotificationObserver observer4( 186 content::NotificationService::AllSources()); 187 chrome::ShowSingletonTab(browser(), bookmarks); 188 observer4.Wait(); 189 tab_count++; 190 host_count++; 191 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 192 tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1); 193 rph3 = tab1->GetRenderProcessHost(); 194 EXPECT_EQ(tab1->GetURL(), bookmarks); 195 EXPECT_EQ(host_count, RenderProcessHostCount()); 196 EXPECT_NE(rph1, rph3); 197 EXPECT_NE(rph2, rph3); 198 } 199}; 200 201 202class ChromeRenderProcessHostTestWithCommandLine 203 : public ChromeRenderProcessHostTest { 204 protected: 205 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 206 command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1"); 207 } 208}; 209 210IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessPerTab) { 211 // Set max renderers to 1 to force running out of processes. 212 content::RenderProcessHost::SetMaxRendererProcessCount(1); 213 214 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 215 parsed_command_line.AppendSwitch(switches::kProcessPerTab); 216 217 int tab_count = 1; 218 int host_count = 1; 219 220 // Change the first tab to be the new tab page (TYPE_WEBUI). 221 GURL omnibox(chrome::kChromeUIOmniboxURL); 222 ui_test_utils::NavigateToURL(browser(), omnibox); 223 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 224 EXPECT_EQ(host_count, RenderProcessHostCount()); 225 226 // Create a new TYPE_TABBED tab. It should be in its own process. 227 GURL page1("data:text/html,hello world1"); 228 ui_test_utils::WindowedTabAddedNotificationObserver observer1( 229 content::NotificationService::AllSources()); 230 chrome::ShowSingletonTab(browser(), page1); 231 observer1.Wait(); 232 tab_count++; 233 host_count++; 234 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 235 EXPECT_EQ(host_count, RenderProcessHostCount()); 236 237 // Create another TYPE_TABBED tab. It should share the previous process. 238 GURL page2("data:text/html,hello world2"); 239 ui_test_utils::WindowedTabAddedNotificationObserver observer2( 240 content::NotificationService::AllSources()); 241 chrome::ShowSingletonTab(browser(), page2); 242 observer2.Wait(); 243 tab_count++; 244 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 245 EXPECT_EQ(host_count, RenderProcessHostCount()); 246 247 // Create another omnibox tab. It should share the process with the other 248 // WebUI. 249 ui_test_utils::NavigateToURLWithDisposition( 250 browser(), omnibox, NEW_FOREGROUND_TAB, 251 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); 252 tab_count++; 253 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 254 EXPECT_EQ(host_count, RenderProcessHostCount()); 255 256 // Create another omnibox tab. It should share the process with the other 257 // WebUI. 258 ui_test_utils::NavigateToURLWithDisposition( 259 browser(), omnibox, NEW_FOREGROUND_TAB, 260 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); 261 tab_count++; 262 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 263 EXPECT_EQ(host_count, RenderProcessHostCount()); 264} 265 266// We don't change process priorities on Mac or Posix because the user lacks the 267// permission to raise a process' priority even after lowering it. 268#if defined(OS_WIN) || defined(OS_LINUX) 269IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, Backgrounding) { 270 if (!base::Process::CanBackgroundProcesses()) { 271 LOG(ERROR) << "Can't background processes"; 272 return; 273 } 274 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 275 parsed_command_line.AppendSwitch(switches::kProcessPerTab); 276 277 // Change the first tab to be the omnibox page (TYPE_WEBUI). 278 GURL omnibox(chrome::kChromeUIOmniboxURL); 279 ui_test_utils::NavigateToURL(browser(), omnibox); 280 281 // Create a new tab. It should be foreground. 282 GURL page1("data:text/html,hello world1"); 283 base::ProcessHandle pid1 = ShowSingletonTab(page1); 284 EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded()); 285 286 // Create another tab. It should be foreground, and the first tab should 287 // now be background. 288 GURL page2("data:text/html,hello world2"); 289 base::ProcessHandle pid2 = ShowSingletonTab(page2); 290 EXPECT_NE(pid1, pid2); 291 EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded()); 292 EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded()); 293 294 // Load another tab in background. The renderer of the new tab should be 295 // backgrounded, while visibility of the other renderers should not change. 296 GURL page3("data:text/html,hello world3"); 297 base::ProcessHandle pid3 = OpenBackgroundTab(page3); 298 EXPECT_NE(pid3, pid1); 299 EXPECT_NE(pid3, pid2); 300 EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded()); 301 EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded()); 302 EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded()); 303 304 // Navigate back to the first page. Its renderer should be in foreground 305 // again while the other renderers should be backgrounded. 306 EXPECT_EQ(pid1, ShowSingletonTab(page1)); 307 EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded()); 308 EXPECT_TRUE(base::Process(pid2).IsProcessBackgrounded()); 309 EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded()); 310} 311#endif 312 313// TODO(nasko): crbug.com/173137 314#if defined(OS_WIN) 315#define MAYBE_ProcessOverflow DISABLED_ProcessOverflow 316#else 317#define MAYBE_ProcessOverflow ProcessOverflow 318#endif 319 320IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_ProcessOverflow) { 321 // Set max renderers to 1 to force running out of processes. 322 content::RenderProcessHost::SetMaxRendererProcessCount(1); 323 TestProcessOverflow(); 324} 325 326// Variation of the ProcessOverflow test, which is driven through command line 327// parameter instead of direct function call into the class. 328IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine, 329 ProcessOverflow) { 330 TestProcessOverflow(); 331} 332 333// Ensure that DevTools opened to debug DevTools is launched in a separate 334// process when --process-per-tab is set. See crbug.com/69873. 335IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, 336 DevToolsOnSelfInOwnProcessPPT) { 337#if defined(OS_WIN) && defined(USE_ASH) 338 // Disable this test in Metro+Ash for now (http://crbug.com/262796). 339 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) 340 return; 341#endif 342 343 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 344 parsed_command_line.AppendSwitch(switches::kProcessPerTab); 345 346 int tab_count = 1; 347 int host_count = 1; 348 349 GURL page1("data:text/html,hello world1"); 350 ui_test_utils::WindowedTabAddedNotificationObserver observer1( 351 content::NotificationService::AllSources()); 352 chrome::ShowSingletonTab(browser(), page1); 353 observer1.Wait(); 354 tab_count++; 355 host_count++; 356 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 357 EXPECT_EQ(host_count, RenderProcessHostCount()); 358 359 // DevTools start in docked mode (no new tab), in a separate process. 360 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect()); 361 host_count++; 362 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 363 EXPECT_EQ(host_count, RenderProcessHostCount()); 364 365 RenderViewHost* devtools = FindFirstDevToolsHost(); 366 DCHECK(devtools); 367 368 // DevTools start in a separate process. 369 DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect()); 370 host_count++; 371 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 372 EXPECT_EQ(host_count, RenderProcessHostCount()); 373 374 // close docked devtools 375 content::WindowedNotificationObserver close_observer( 376 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 377 content::Source<WebContents>(WebContents::FromRenderViewHost(devtools))); 378 379 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle()); 380 close_observer.Wait(); 381} 382 383// Ensure that DevTools opened to debug DevTools is launched in a separate 384// process. See crbug.com/69873. 385IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, 386 DevToolsOnSelfInOwnProcess) { 387#if defined(OS_WIN) && defined(USE_ASH) 388 // Disable this test in Metro+Ash for now (http://crbug.com/262796). 389 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) 390 return; 391#endif 392 393 int tab_count = 1; 394 int host_count = 1; 395 396 GURL page1("data:text/html,hello world1"); 397 ui_test_utils::WindowedTabAddedNotificationObserver observer1( 398 content::NotificationService::AllSources()); 399 chrome::ShowSingletonTab(browser(), page1); 400 observer1.Wait(); 401 tab_count++; 402 host_count++; 403 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 404 EXPECT_EQ(host_count, RenderProcessHostCount()); 405 406 // DevTools start in docked mode (no new tab), in a separate process. 407 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect()); 408 host_count++; 409 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 410 EXPECT_EQ(host_count, RenderProcessHostCount()); 411 412 RenderViewHost* devtools = FindFirstDevToolsHost(); 413 DCHECK(devtools); 414 415 // DevTools start in a separate process. 416 DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect()); 417 host_count++; 418 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 419 EXPECT_EQ(host_count, RenderProcessHostCount()); 420 421 // close docked devtools 422 content::WindowedNotificationObserver close_observer( 423 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 424 content::Source<content::WebContents>( 425 WebContents::FromRenderViewHost(devtools))); 426 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle()); 427 close_observer.Wait(); 428} 429 430// This class's goal is to close the browser window when a renderer process has 431// crashed. It does so by monitoring WebContents for RenderProcessGone event and 432// closing the passed in TabStripModel. This is used in the following test case. 433class WindowDestroyer : public content::WebContentsObserver { 434 public: 435 WindowDestroyer(content::WebContents* web_contents, TabStripModel* model) 436 : content::WebContentsObserver(web_contents), 437 tab_strip_model_(model) { 438 } 439 440 virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE { 441 // Wait for the window to be destroyed, which will ensure all other 442 // RenderViewHost objects are deleted before we return and proceed with 443 // the next iteration of notifications. 444 content::WindowedNotificationObserver observer( 445 chrome::NOTIFICATION_BROWSER_CLOSED, 446 content::NotificationService::AllSources()); 447 tab_strip_model_->CloseAllTabs(); 448 observer.Wait(); 449 } 450 451 private: 452 TabStripModel* tab_strip_model_; 453 454 DISALLOW_COPY_AND_ASSIGN(WindowDestroyer); 455}; 456 457// Test to ensure that while iterating through all listeners in 458// RenderProcessHost and invalidating them, we remove them properly and don't 459// access already freed objects. See http://crbug.com/255524. 460IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, 461 CloseAllTabsDuringProcessDied) { 462 GURL url(chrome::kChromeUIOmniboxURL); 463 464 ui_test_utils::NavigateToURL(browser(), url); 465 ui_test_utils::NavigateToURLWithDisposition( 466 browser(), url, NEW_BACKGROUND_TAB, 467 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); 468 469 EXPECT_EQ(2, browser()->tab_strip_model()->count()); 470 471 WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0); 472 WebContents* wc2 = browser()->tab_strip_model()->GetWebContentsAt(1); 473 EXPECT_EQ(wc1->GetRenderProcessHost(), wc2->GetRenderProcessHost()); 474 475 // Create an object that will close the window on a process crash. 476 WindowDestroyer destroyer(wc1, browser()->tab_strip_model()); 477 478 // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED, 479 // since the latter is not implemented on OSX and the test will timeout, 480 // causing it to fail. 481 content::WindowedNotificationObserver observer( 482 chrome::NOTIFICATION_BROWSER_CLOSED, 483 content::NotificationService::AllSources()); 484 485 // Kill the renderer process, simulating a crash. This should the ProcessDied 486 // method to be called. Alternatively, RenderProcessHost::OnChannelError can 487 // be called to directly force a call to ProcessDied. 488 base::KillProcess(wc1->GetRenderProcessHost()->GetHandle(), -1, true); 489 490 observer.Wait(); 491} 492