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