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