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