Home | History | Annotate | Download | only in renderer_host
      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 
     26 using content::RenderViewHost;
     27 using content::RenderWidgetHost;
     28 using content::WebContents;
     29 
     30 namespace {
     31 
     32 int 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 
     44 RenderViewHost* FindFirstDevToolsHost() {
     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 host;
     57   }
     58   return NULL;
     59 }
     60 
     61 }  // namespace
     62 
     63 class 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 
    202 class ChromeRenderProcessHostTestWithCommandLine
    203     : public ChromeRenderProcessHostTest {
    204  protected:
    205   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    206     command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
    207   }
    208 };
    209 
    210 IN_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 // flaky, disabling on branch
    269 #if defined(OS_WIN) || defined(OS_LINUX)
    270 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, DISABLED_Backgrounding) {
    271   if (!base::Process::CanBackgroundProcesses()) {
    272     LOG(ERROR) << "Can't background processes";
    273     return;
    274   }
    275   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
    276   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
    277 
    278   // Change the first tab to be the omnibox page (TYPE_WEBUI).
    279   GURL omnibox(chrome::kChromeUIOmniboxURL);
    280   ui_test_utils::NavigateToURL(browser(), omnibox);
    281 
    282   // Create a new tab. It should be foreground.
    283   GURL page1("data:text/html,hello world1");
    284   base::ProcessHandle pid1 = ShowSingletonTab(page1);
    285   EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
    286 
    287   // Create another tab. It should be foreground, and the first tab should
    288   // now be background.
    289   GURL page2("data:text/html,hello world2");
    290   base::ProcessHandle pid2 = ShowSingletonTab(page2);
    291   EXPECT_NE(pid1, pid2);
    292   EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
    293   EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
    294 
    295   // Load another tab in background. The renderer of the new tab should be
    296   // backgrounded, while visibility of the other renderers should not change.
    297   GURL page3("data:text/html,hello world3");
    298   base::ProcessHandle pid3 = OpenBackgroundTab(page3);
    299   EXPECT_NE(pid3, pid1);
    300   EXPECT_NE(pid3, pid2);
    301   EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
    302   EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
    303   EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded());
    304 
    305   // Navigate back to the first page. Its renderer should be in foreground
    306   // again while the other renderers should be backgrounded.
    307   EXPECT_EQ(pid1, ShowSingletonTab(page1));
    308   EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
    309   EXPECT_TRUE(base::Process(pid2).IsProcessBackgrounded());
    310   EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded());
    311 }
    312 #endif
    313 
    314 // TODO(nasko): crbug.com/173137
    315 #if defined(OS_WIN)
    316 #define MAYBE_ProcessOverflow DISABLED_ProcessOverflow
    317 #else
    318 #define MAYBE_ProcessOverflow ProcessOverflow
    319 #endif
    320 
    321 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_ProcessOverflow) {
    322   // Set max renderers to 1 to force running out of processes.
    323   content::RenderProcessHost::SetMaxRendererProcessCount(1);
    324   TestProcessOverflow();
    325 }
    326 
    327 // Variation of the ProcessOverflow test, which is driven through command line
    328 // parameter instead of direct function call into the class.
    329 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
    330                        ProcessOverflow) {
    331   TestProcessOverflow();
    332 }
    333 
    334 // Ensure that DevTools opened to debug DevTools is launched in a separate
    335 // process when --process-per-tab is set. See crbug.com/69873.
    336 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
    337                        DevToolsOnSelfInOwnProcessPPT) {
    338 #if defined(OS_WIN) && defined(USE_ASH)
    339   // Disable this test in Metro+Ash for now (http://crbug.com/262796).
    340   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
    341     return;
    342 #endif
    343 
    344   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
    345   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
    346 
    347   int tab_count = 1;
    348   int host_count = 1;
    349 
    350   GURL page1("data:text/html,hello world1");
    351   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
    352       content::NotificationService::AllSources());
    353   chrome::ShowSingletonTab(browser(), page1);
    354   observer1.Wait();
    355   tab_count++;
    356   host_count++;
    357   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    358   EXPECT_EQ(host_count, RenderProcessHostCount());
    359 
    360   // DevTools start in docked mode (no new tab), in a separate process.
    361   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
    362   host_count++;
    363   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    364   EXPECT_EQ(host_count, RenderProcessHostCount());
    365 
    366   RenderViewHost* devtools = FindFirstDevToolsHost();
    367   DCHECK(devtools);
    368 
    369   // DevTools start in a separate process.
    370   DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
    371   host_count++;
    372   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    373   EXPECT_EQ(host_count, RenderProcessHostCount());
    374 
    375   // close docked devtools
    376   content::WindowedNotificationObserver close_observer(
    377       content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    378       content::Source<WebContents>(WebContents::FromRenderViewHost(devtools)));
    379 
    380   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
    381   close_observer.Wait();
    382 }
    383 
    384 // Ensure that DevTools opened to debug DevTools is launched in a separate
    385 // process. See crbug.com/69873.
    386 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
    387                        DevToolsOnSelfInOwnProcess) {
    388 #if defined(OS_WIN) && defined(USE_ASH)
    389   // Disable this test in Metro+Ash for now (http://crbug.com/262796).
    390   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
    391     return;
    392 #endif
    393 
    394   int tab_count = 1;
    395   int host_count = 1;
    396 
    397   GURL page1("data:text/html,hello world1");
    398   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
    399       content::NotificationService::AllSources());
    400   chrome::ShowSingletonTab(browser(), page1);
    401   observer1.Wait();
    402   tab_count++;
    403   host_count++;
    404   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    405   EXPECT_EQ(host_count, RenderProcessHostCount());
    406 
    407   // DevTools start in docked mode (no new tab), in a separate process.
    408   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
    409   host_count++;
    410   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    411   EXPECT_EQ(host_count, RenderProcessHostCount());
    412 
    413   RenderViewHost* devtools = FindFirstDevToolsHost();
    414   DCHECK(devtools);
    415 
    416   // DevTools start in a separate process.
    417   DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
    418   host_count++;
    419   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    420   EXPECT_EQ(host_count, RenderProcessHostCount());
    421 
    422   // close docked devtools
    423   content::WindowedNotificationObserver close_observer(
    424       content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    425       content::Source<content::WebContents>(
    426           WebContents::FromRenderViewHost(devtools)));
    427   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
    428   close_observer.Wait();
    429 }
    430 
    431 // This class's goal is to close the browser window when a renderer process has
    432 // crashed. It does so by monitoring WebContents for RenderProcessGone event and
    433 // closing the passed in TabStripModel. This is used in the following test case.
    434 class WindowDestroyer : public content::WebContentsObserver {
    435  public:
    436   WindowDestroyer(content::WebContents* web_contents, TabStripModel* model)
    437       : content::WebContentsObserver(web_contents),
    438         tab_strip_model_(model) {
    439   }
    440 
    441   virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
    442     // Wait for the window to be destroyed, which will ensure all other
    443     // RenderViewHost objects are deleted before we return and proceed with
    444     // the next iteration of notifications.
    445     content::WindowedNotificationObserver observer(
    446         chrome::NOTIFICATION_BROWSER_CLOSED,
    447         content::NotificationService::AllSources());
    448     tab_strip_model_->CloseAllTabs();
    449     observer.Wait();
    450   }
    451 
    452  private:
    453   TabStripModel* tab_strip_model_;
    454 
    455   DISALLOW_COPY_AND_ASSIGN(WindowDestroyer);
    456 };
    457 
    458 // Test to ensure that while iterating through all listeners in
    459 // RenderProcessHost and invalidating them, we remove them properly and don't
    460 // access already freed objects. See http://crbug.com/255524.
    461 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
    462                        CloseAllTabsDuringProcessDied) {
    463   GURL url(chrome::kChromeUIOmniboxURL);
    464 
    465   ui_test_utils::NavigateToURL(browser(), url);
    466   ui_test_utils::NavigateToURLWithDisposition(
    467       browser(), url, NEW_BACKGROUND_TAB,
    468       ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
    469 
    470   EXPECT_EQ(2, browser()->tab_strip_model()->count());
    471 
    472   WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0);
    473   WebContents* wc2 = browser()->tab_strip_model()->GetWebContentsAt(1);
    474   EXPECT_EQ(wc1->GetRenderProcessHost(), wc2->GetRenderProcessHost());
    475 
    476   // Create an object that will close the window on a process crash.
    477   WindowDestroyer destroyer(wc1, browser()->tab_strip_model());
    478 
    479   // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED,
    480   // since the latter is not implemented on OSX and the test will timeout,
    481   // causing it to fail.
    482   content::WindowedNotificationObserver observer(
    483       chrome::NOTIFICATION_BROWSER_CLOSED,
    484       content::NotificationService::AllSources());
    485 
    486   // Kill the renderer process, simulating a crash. This should the ProcessDied
    487   // method to be called. Alternatively, RenderProcessHost::OnChannelError can
    488   // be called to directly force a call to ProcessDied.
    489   base::KillProcess(wc1->GetRenderProcessHost()->GetHandle(), -1, true);
    490 
    491   observer.Wait();
    492 }
    493