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