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 26 using content::RenderViewHost; 27 using content::RenderWidgetHost; 28 using content::WebContents; 29 30 namespace { 31 32 int 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 44 RenderViewHost* 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 63 class 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 202 class ChromeRenderProcessHostTestWithCommandLine 203 : public ChromeRenderProcessHostTest { 204 protected: 205 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 206 command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1"); 207 } 208 }; 209 210 IN_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 // flaky, disabling on branch 269 #if defined(OS_WIN) || defined(OS_LINUX) 270 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, DISABLED_Backgrounding) { 271 if (!base::Process::CanBackgroundProcesses()) { 272 LOG(ERROR) << "Can't background processes"; 273 return; 274 } 275 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 276 parsed_command_line.AppendSwitch(switches::kProcessPerTab); 277 278 // Change the first tab to be the omnibox page (TYPE_WEBUI). 279 GURL omnibox(chrome::kChromeUIOmniboxURL); 280 ui_test_utils::NavigateToURL(browser(), omnibox); 281 282 // Create a new tab. It should be foreground. 283 GURL page1("data:text/html,hello world1"); 284 base::ProcessHandle pid1 = ShowSingletonTab(page1); 285 EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded()); 286 287 // Create another tab. It should be foreground, and the first tab should 288 // now be background. 289 GURL page2("data:text/html,hello world2"); 290 base::ProcessHandle pid2 = ShowSingletonTab(page2); 291 EXPECT_NE(pid1, pid2); 292 EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded()); 293 EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded()); 294 295 // Load another tab in background. The renderer of the new tab should be 296 // backgrounded, while visibility of the other renderers should not change. 297 GURL page3("data:text/html,hello world3"); 298 base::ProcessHandle pid3 = OpenBackgroundTab(page3); 299 EXPECT_NE(pid3, pid1); 300 EXPECT_NE(pid3, pid2); 301 EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded()); 302 EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded()); 303 EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded()); 304 305 // Navigate back to the first page. Its renderer should be in foreground 306 // again while the other renderers should be backgrounded. 307 EXPECT_EQ(pid1, ShowSingletonTab(page1)); 308 EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded()); 309 EXPECT_TRUE(base::Process(pid2).IsProcessBackgrounded()); 310 EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded()); 311 } 312 #endif 313 314 // TODO(nasko): crbug.com/173137 315 #if defined(OS_WIN) 316 #define MAYBE_ProcessOverflow DISABLED_ProcessOverflow 317 #else 318 #define MAYBE_ProcessOverflow ProcessOverflow 319 #endif 320 321 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_ProcessOverflow) { 322 // Set max renderers to 1 to force running out of processes. 323 content::RenderProcessHost::SetMaxRendererProcessCount(1); 324 TestProcessOverflow(); 325 } 326 327 // Variation of the ProcessOverflow test, which is driven through command line 328 // parameter instead of direct function call into the class. 329 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine, 330 ProcessOverflow) { 331 TestProcessOverflow(); 332 } 333 334 // Ensure that DevTools opened to debug DevTools is launched in a separate 335 // process when --process-per-tab is set. See crbug.com/69873. 336 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, 337 DevToolsOnSelfInOwnProcessPPT) { 338 #if defined(OS_WIN) && defined(USE_ASH) 339 // Disable this test in Metro+Ash for now (http://crbug.com/262796). 340 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) 341 return; 342 #endif 343 344 CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 345 parsed_command_line.AppendSwitch(switches::kProcessPerTab); 346 347 int tab_count = 1; 348 int host_count = 1; 349 350 GURL page1("data:text/html,hello world1"); 351 ui_test_utils::WindowedTabAddedNotificationObserver observer1( 352 content::NotificationService::AllSources()); 353 chrome::ShowSingletonTab(browser(), page1); 354 observer1.Wait(); 355 tab_count++; 356 host_count++; 357 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 358 EXPECT_EQ(host_count, RenderProcessHostCount()); 359 360 // DevTools start in docked mode (no new tab), in a separate process. 361 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect()); 362 host_count++; 363 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 364 EXPECT_EQ(host_count, RenderProcessHostCount()); 365 366 RenderViewHost* devtools = FindFirstDevToolsHost(); 367 DCHECK(devtools); 368 369 // DevTools start in a separate process. 370 DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect()); 371 host_count++; 372 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 373 EXPECT_EQ(host_count, RenderProcessHostCount()); 374 375 // close docked devtools 376 content::WindowedNotificationObserver close_observer( 377 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 378 content::Source<WebContents>(WebContents::FromRenderViewHost(devtools))); 379 380 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle()); 381 close_observer.Wait(); 382 } 383 384 // Ensure that DevTools opened to debug DevTools is launched in a separate 385 // process. See crbug.com/69873. 386 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, 387 DevToolsOnSelfInOwnProcess) { 388 #if defined(OS_WIN) && defined(USE_ASH) 389 // Disable this test in Metro+Ash for now (http://crbug.com/262796). 390 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) 391 return; 392 #endif 393 394 int tab_count = 1; 395 int host_count = 1; 396 397 GURL page1("data:text/html,hello world1"); 398 ui_test_utils::WindowedTabAddedNotificationObserver observer1( 399 content::NotificationService::AllSources()); 400 chrome::ShowSingletonTab(browser(), page1); 401 observer1.Wait(); 402 tab_count++; 403 host_count++; 404 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 405 EXPECT_EQ(host_count, RenderProcessHostCount()); 406 407 // DevTools start in docked mode (no new tab), in a separate process. 408 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect()); 409 host_count++; 410 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 411 EXPECT_EQ(host_count, RenderProcessHostCount()); 412 413 RenderViewHost* devtools = FindFirstDevToolsHost(); 414 DCHECK(devtools); 415 416 // DevTools start in a separate process. 417 DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect()); 418 host_count++; 419 EXPECT_EQ(tab_count, browser()->tab_strip_model()->count()); 420 EXPECT_EQ(host_count, RenderProcessHostCount()); 421 422 // close docked devtools 423 content::WindowedNotificationObserver close_observer( 424 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 425 content::Source<content::WebContents>( 426 WebContents::FromRenderViewHost(devtools))); 427 chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle()); 428 close_observer.Wait(); 429 } 430 431 // This class's goal is to close the browser window when a renderer process has 432 // crashed. It does so by monitoring WebContents for RenderProcessGone event and 433 // closing the passed in TabStripModel. This is used in the following test case. 434 class WindowDestroyer : public content::WebContentsObserver { 435 public: 436 WindowDestroyer(content::WebContents* web_contents, TabStripModel* model) 437 : content::WebContentsObserver(web_contents), 438 tab_strip_model_(model) { 439 } 440 441 virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE { 442 // Wait for the window to be destroyed, which will ensure all other 443 // RenderViewHost objects are deleted before we return and proceed with 444 // the next iteration of notifications. 445 content::WindowedNotificationObserver observer( 446 chrome::NOTIFICATION_BROWSER_CLOSED, 447 content::NotificationService::AllSources()); 448 tab_strip_model_->CloseAllTabs(); 449 observer.Wait(); 450 } 451 452 private: 453 TabStripModel* tab_strip_model_; 454 455 DISALLOW_COPY_AND_ASSIGN(WindowDestroyer); 456 }; 457 458 // Test to ensure that while iterating through all listeners in 459 // RenderProcessHost and invalidating them, we remove them properly and don't 460 // access already freed objects. See http://crbug.com/255524. 461 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, 462 CloseAllTabsDuringProcessDied) { 463 GURL url(chrome::kChromeUIOmniboxURL); 464 465 ui_test_utils::NavigateToURL(browser(), url); 466 ui_test_utils::NavigateToURLWithDisposition( 467 browser(), url, NEW_BACKGROUND_TAB, 468 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); 469 470 EXPECT_EQ(2, browser()->tab_strip_model()->count()); 471 472 WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0); 473 WebContents* wc2 = browser()->tab_strip_model()->GetWebContentsAt(1); 474 EXPECT_EQ(wc1->GetRenderProcessHost(), wc2->GetRenderProcessHost()); 475 476 // Create an object that will close the window on a process crash. 477 WindowDestroyer destroyer(wc1, browser()->tab_strip_model()); 478 479 // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED, 480 // since the latter is not implemented on OSX and the test will timeout, 481 // causing it to fail. 482 content::WindowedNotificationObserver observer( 483 chrome::NOTIFICATION_BROWSER_CLOSED, 484 content::NotificationService::AllSources()); 485 486 // Kill the renderer process, simulating a crash. This should the ProcessDied 487 // method to be called. Alternatively, RenderProcessHost::OnChannelError can 488 // be called to directly force a call to ProcessDied. 489 base::KillProcess(wc1->GetRenderProcessHost()->GetHandle(), -1, true); 490 491 observer.Wait(); 492 } 493