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