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