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