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