1// Copyright (c) 2011 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/process_util.h"
6#include "chrome/browser/browser_process.h"
7#include "chrome/browser/extensions/extension_browsertest.h"
8#include "chrome/browser/extensions/extension_host.h"
9#include "chrome/browser/extensions/extension_process_manager.h"
10#include "chrome/browser/extensions/extension_service.h"
11#include "chrome/browser/notifications/balloon_host.h"
12#include "chrome/browser/notifications/notification.h"
13#include "chrome/browser/notifications/notification_delegate.h"
14#include "chrome/browser/notifications/notification_ui_manager.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/tabs/tab_strip_model.h"
17#include "chrome/browser/ui/browser.h"
18#include "chrome/test/ui_test_utils.h"
19#include "content/browser/renderer_host/render_process_host.h"
20#include "content/browser/renderer_host/render_view_host.h"
21#include "content/browser/tab_contents/tab_contents.h"
22#include "content/common/result_codes.h"
23
24class ExtensionCrashRecoveryTest : public ExtensionBrowserTest {
25 protected:
26  ExtensionService* GetExtensionService() {
27    return browser()->profile()->GetExtensionService();
28  }
29
30  ExtensionProcessManager* GetExtensionProcessManager() {
31    return browser()->profile()->GetExtensionProcessManager();
32  }
33
34  Balloon* GetNotificationDelegate(size_t index) {
35    NotificationUIManager* manager =
36        g_browser_process->notification_ui_manager();
37    BalloonCollection::Balloons balloons =
38        manager->balloon_collection()->GetActiveBalloons();
39    return balloons.at(index);
40  }
41
42  void AcceptNotification(size_t index) {
43    Balloon* balloon = GetNotificationDelegate(index);
44    ASSERT_TRUE(balloon);
45    balloon->OnClick();
46    WaitForExtensionLoad();
47  }
48
49  void CancelNotification(size_t index) {
50    Balloon* balloon = GetNotificationDelegate(index);
51    NotificationUIManager* manager =
52        g_browser_process->notification_ui_manager();
53    manager->CancelById(balloon->notification().notification_id());
54  }
55
56  size_t CountBalloons() {
57    NotificationUIManager* manager =
58        g_browser_process->notification_ui_manager();
59    BalloonCollection::Balloons balloons =
60        manager->balloon_collection()->GetActiveBalloons();
61    return balloons.size();
62  }
63
64  void CrashExtension(size_t index) {
65    ASSERT_LT(index, GetExtensionService()->extensions()->size());
66    const Extension* extension =
67        GetExtensionService()->extensions()->at(index);
68    ASSERT_TRUE(extension);
69    std::string extension_id(extension->id());
70    ExtensionHost* extension_host =
71        GetExtensionProcessManager()->GetBackgroundHostForExtension(extension);
72    ASSERT_TRUE(extension_host);
73
74    RenderProcessHost* extension_rph =
75        extension_host->render_view_host()->process();
76    base::KillProcess(extension_rph->GetHandle(), ResultCodes::KILLED, false);
77    ASSERT_TRUE(WaitForExtensionCrash(extension_id));
78    ASSERT_FALSE(
79        GetExtensionProcessManager()->GetBackgroundHostForExtension(extension));
80  }
81
82  void CheckExtensionConsistency(size_t index) {
83    ASSERT_LT(index, GetExtensionService()->extensions()->size());
84    const Extension* extension =
85        GetExtensionService()->extensions()->at(index);
86    ASSERT_TRUE(extension);
87    ExtensionHost* extension_host =
88        GetExtensionProcessManager()->GetBackgroundHostForExtension(extension);
89    ASSERT_TRUE(extension_host);
90    ASSERT_TRUE(GetExtensionProcessManager()->HasExtensionHost(extension_host));
91    ASSERT_TRUE(extension_host->IsRenderViewLive());
92    ASSERT_EQ(extension_host->render_view_host()->process(),
93        GetExtensionProcessManager()->GetExtensionProcess(extension->id()));
94  }
95
96  void LoadTestExtension() {
97    ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
98    const size_t size_before = GetExtensionService()->extensions()->size();
99    ASSERT_TRUE(LoadExtension(
100        test_data_dir_.AppendASCII("common").AppendASCII("background_page")));
101    const Extension* extension = GetExtensionService()->extensions()->back();
102    ASSERT_TRUE(extension);
103    first_extension_id_ = extension->id();
104    CheckExtensionConsistency(size_before);
105  }
106
107  void LoadSecondExtension() {
108    int offset = GetExtensionService()->extensions()->size();
109    ASSERT_TRUE(LoadExtension(
110        test_data_dir_.AppendASCII("install").AppendASCII("install")));
111    const Extension* extension =
112        GetExtensionService()->extensions()->at(offset);
113    ASSERT_TRUE(extension);
114    second_extension_id_ = extension->id();
115    CheckExtensionConsistency(offset);
116  }
117
118  std::string first_extension_id_;
119  std::string second_extension_id_;
120};
121
122IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, Basic) {
123  const size_t size_before = GetExtensionService()->extensions()->size();
124  const size_t crash_size_before =
125      GetExtensionService()->terminated_extensions()->size();
126  LoadTestExtension();
127  CrashExtension(size_before);
128  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
129  ASSERT_EQ(crash_size_before + 1,
130            GetExtensionService()->terminated_extensions()->size());
131  AcceptNotification(0);
132
133  SCOPED_TRACE("after clicking the balloon");
134  CheckExtensionConsistency(size_before);
135  ASSERT_EQ(crash_size_before,
136            GetExtensionService()->terminated_extensions()->size());
137}
138
139IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, CloseAndReload) {
140  const size_t size_before = GetExtensionService()->extensions()->size();
141  const size_t crash_size_before =
142      GetExtensionService()->terminated_extensions()->size();
143  LoadTestExtension();
144  CrashExtension(size_before);
145
146  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
147  ASSERT_EQ(crash_size_before + 1,
148            GetExtensionService()->terminated_extensions()->size());
149
150  CancelNotification(0);
151  ReloadExtension(first_extension_id_);
152
153  SCOPED_TRACE("after reloading");
154  CheckExtensionConsistency(size_before);
155  ASSERT_EQ(crash_size_before,
156            GetExtensionService()->terminated_extensions()->size());
157}
158
159IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, ReloadIndependently) {
160  const size_t size_before = GetExtensionService()->extensions()->size();
161  LoadTestExtension();
162  CrashExtension(size_before);
163  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
164
165  ReloadExtension(first_extension_id_);
166
167  SCOPED_TRACE("after reloading");
168  CheckExtensionConsistency(size_before);
169
170  TabContents* current_tab = browser()->GetSelectedTabContents();
171  ASSERT_TRUE(current_tab);
172
173  // The balloon should automatically hide after the extension is successfully
174  // reloaded.
175  ASSERT_EQ(0U, CountBalloons());
176}
177
178IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest,
179                       ReloadIndependentlyChangeTabs) {
180  const size_t size_before = GetExtensionService()->extensions()->size();
181  LoadTestExtension();
182  CrashExtension(size_before);
183  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
184
185  TabContents* original_tab = browser()->GetSelectedTabContents();
186  ASSERT_TRUE(original_tab);
187  ASSERT_EQ(1U, CountBalloons());
188
189  // Open a new tab, but the balloon will still be there.
190  browser()->NewTab();
191  TabContents* new_current_tab = browser()->GetSelectedTabContents();
192  ASSERT_TRUE(new_current_tab);
193  ASSERT_NE(new_current_tab, original_tab);
194  ASSERT_EQ(1U, CountBalloons());
195
196  ReloadExtension(first_extension_id_);
197
198  SCOPED_TRACE("after reloading");
199  CheckExtensionConsistency(size_before);
200
201  // The balloon should automatically hide after the extension is successfully
202  // reloaded.
203  ASSERT_EQ(0U, CountBalloons());
204}
205
206IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest,
207                       ReloadIndependentlyNavigatePage) {
208  const size_t size_before = GetExtensionService()->extensions()->size();
209  LoadTestExtension();
210  CrashExtension(size_before);
211  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
212
213  TabContents* current_tab = browser()->GetSelectedTabContents();
214  ASSERT_TRUE(current_tab);
215  ASSERT_EQ(1U, CountBalloons());
216
217  // Navigate to another page.
218  ui_test_utils::NavigateToURL(browser(),
219      ui_test_utils::GetTestUrl(FilePath(FilePath::kCurrentDirectory),
220                                FilePath(FILE_PATH_LITERAL("title1.html"))));
221  ASSERT_EQ(1U, CountBalloons());
222
223  ReloadExtension(first_extension_id_);
224
225  SCOPED_TRACE("after reloading");
226  CheckExtensionConsistency(size_before);
227
228  // The infobar should automatically hide after the extension is successfully
229  // reloaded.
230  ASSERT_EQ(0U, CountBalloons());
231}
232
233IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest,
234                       ReloadIndependentlyTwoInfoBars) {
235  const size_t size_before = GetExtensionService()->extensions()->size();
236  LoadTestExtension();
237
238  // Open a new window so that there will be an info bar in each.
239  Browser* browser2 = CreateBrowser(browser()->profile());
240
241  CrashExtension(size_before);
242  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
243
244  TabContents* current_tab = browser()->GetSelectedTabContents();
245  ASSERT_TRUE(current_tab);
246  ASSERT_EQ(1U, CountBalloons());
247
248  TabContents* current_tab2 = browser2->GetSelectedTabContents();
249  ASSERT_TRUE(current_tab2);
250  ASSERT_EQ(1U, CountBalloons());
251
252  ReloadExtension(first_extension_id_);
253
254  SCOPED_TRACE("after reloading");
255  CheckExtensionConsistency(size_before);
256
257  // Both infobars should automatically hide after the extension is successfully
258  // reloaded.
259  ASSERT_EQ(0U, CountBalloons());
260  ASSERT_EQ(0U, CountBalloons());
261}
262
263IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest,
264                       ReloadIndependentlyTwoInfoBarsSameBrowser) {
265  const size_t size_before = GetExtensionService()->extensions()->size();
266  LoadTestExtension();
267
268  // Open a new window so that there will be an info bar in each.
269  Browser* browser2 = CreateBrowser(browser()->profile());
270
271  CrashExtension(size_before);
272  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
273
274  TabContents* current_tab = browser()->GetSelectedTabContents();
275  ASSERT_TRUE(current_tab);
276  ASSERT_EQ(1U, CountBalloons());
277
278  TabContents* current_tab2 = browser2->GetSelectedTabContents();
279  ASSERT_TRUE(current_tab2);
280  ASSERT_EQ(1U, CountBalloons());
281
282  // Move second window into first browser so there will be multiple tabs
283  // with the info bar for the same extension in one browser.
284  TabContentsWrapper* contents =
285      browser2->tabstrip_model()->DetachTabContentsAt(0);
286  browser()->tabstrip_model()->AppendTabContents(contents, true);
287  current_tab2 = browser()->GetSelectedTabContents();
288  ASSERT_EQ(1U, CountBalloons());
289  ASSERT_NE(current_tab2, current_tab);
290
291  ReloadExtension(first_extension_id_);
292
293  SCOPED_TRACE("after reloading");
294  CheckExtensionConsistency(size_before);
295
296  // Both infobars should automatically hide after the extension is successfully
297  // reloaded.
298  ASSERT_EQ(0U, CountBalloons());
299  browser()->SelectPreviousTab();
300  ASSERT_EQ(current_tab, browser()->GetSelectedTabContents());
301  ASSERT_EQ(0U, CountBalloons());
302}
303
304// Make sure that when we don't do anything about the crashed extension
305// and close the browser, it doesn't crash. The browser is closed implicitly
306// at the end of each browser test.
307IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, ShutdownWhileCrashed) {
308  const size_t size_before = GetExtensionService()->extensions()->size();
309  LoadTestExtension();
310  CrashExtension(size_before);
311  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
312}
313
314IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, TwoExtensionsCrashFirst) {
315  const size_t size_before = GetExtensionService()->extensions()->size();
316  LoadTestExtension();
317  LoadSecondExtension();
318  CrashExtension(size_before);
319  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
320  AcceptNotification(0);
321
322  SCOPED_TRACE("after clicking the balloon");
323  CheckExtensionConsistency(size_before);
324  CheckExtensionConsistency(size_before + 1);
325}
326
327IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, TwoExtensionsCrashSecond) {
328  const size_t size_before = GetExtensionService()->extensions()->size();
329  LoadTestExtension();
330  LoadSecondExtension();
331  CrashExtension(size_before + 1);
332  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
333  AcceptNotification(0);
334
335  SCOPED_TRACE("after clicking the balloon");
336  CheckExtensionConsistency(size_before);
337  CheckExtensionConsistency(size_before + 1);
338}
339
340IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest,
341                       TwoExtensionsCrashBothAtOnce) {
342  const size_t size_before = GetExtensionService()->extensions()->size();
343  const size_t crash_size_before =
344      GetExtensionService()->terminated_extensions()->size();
345  LoadTestExtension();
346  LoadSecondExtension();
347  CrashExtension(size_before);
348  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
349  ASSERT_EQ(crash_size_before + 1,
350            GetExtensionService()->terminated_extensions()->size());
351  CrashExtension(size_before);
352  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
353  ASSERT_EQ(crash_size_before + 2,
354            GetExtensionService()->terminated_extensions()->size());
355
356  {
357    SCOPED_TRACE("first balloon");
358    AcceptNotification(0);
359    CheckExtensionConsistency(size_before);
360  }
361
362  {
363    SCOPED_TRACE("second balloon");
364    AcceptNotification(0);
365    CheckExtensionConsistency(size_before);
366    CheckExtensionConsistency(size_before + 1);
367  }
368}
369
370IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, TwoExtensionsOneByOne) {
371  const size_t size_before = GetExtensionService()->extensions()->size();
372  LoadTestExtension();
373  CrashExtension(size_before);
374  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
375  LoadSecondExtension();
376  CrashExtension(size_before);
377  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
378
379  {
380    SCOPED_TRACE("first balloon");
381    AcceptNotification(0);
382    CheckExtensionConsistency(size_before);
383  }
384
385  {
386    SCOPED_TRACE("second balloon");
387    AcceptNotification(0);
388    CheckExtensionConsistency(size_before);
389    CheckExtensionConsistency(size_before + 1);
390  }
391}
392
393// Make sure that when we don't do anything about the crashed extensions
394// and close the browser, it doesn't crash. The browser is closed implicitly
395// at the end of each browser test.
396IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest,
397                       TwoExtensionsShutdownWhileCrashed) {
398  const size_t size_before = GetExtensionService()->extensions()->size();
399  LoadTestExtension();
400  CrashExtension(size_before);
401  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
402  LoadSecondExtension();
403  CrashExtension(size_before);
404  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
405}
406
407IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, TwoExtensionsIgnoreFirst) {
408  const size_t size_before = GetExtensionService()->extensions()->size();
409  LoadTestExtension();
410  LoadSecondExtension();
411  CrashExtension(size_before);
412  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
413  CrashExtension(size_before);
414  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
415
416  CancelNotification(0);
417  // Cancelling the balloon at 0 will close the balloon, and the balloon in
418  // index 1 will move into index 0.
419  AcceptNotification(0);
420
421  SCOPED_TRACE("balloons done");
422  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
423  CheckExtensionConsistency(size_before);
424}
425
426IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest,
427                       TwoExtensionsReloadIndependently) {
428  const size_t size_before = GetExtensionService()->extensions()->size();
429  LoadTestExtension();
430  LoadSecondExtension();
431  CrashExtension(size_before);
432  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
433  CrashExtension(size_before);
434  ASSERT_EQ(size_before, GetExtensionService()->extensions()->size());
435
436  {
437    SCOPED_TRACE("first: reload");
438    TabContents* current_tab = browser()->GetSelectedTabContents();
439    ASSERT_TRUE(current_tab);
440    // At the beginning we should have one infobar displayed for each extension.
441    ASSERT_EQ(2U, CountBalloons());
442    ReloadExtension(first_extension_id_);
443    // One of the infobars should hide after the extension is reloaded.
444    ASSERT_EQ(1U, CountBalloons());
445    CheckExtensionConsistency(size_before);
446  }
447
448  {
449    SCOPED_TRACE("second: balloon");
450    AcceptNotification(0);
451    CheckExtensionConsistency(size_before);
452    CheckExtensionConsistency(size_before + 1);
453  }
454}
455
456IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, CrashAndUninstall) {
457  const size_t size_before = GetExtensionService()->extensions()->size();
458  const size_t crash_size_before =
459      GetExtensionService()->terminated_extensions()->size();
460  LoadTestExtension();
461  LoadSecondExtension();
462  CrashExtension(size_before);
463  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
464  ASSERT_EQ(crash_size_before + 1,
465            GetExtensionService()->terminated_extensions()->size());
466
467  ASSERT_EQ(1U, CountBalloons());
468  UninstallExtension(first_extension_id_);
469  MessageLoop::current()->RunAllPending();
470
471  SCOPED_TRACE("after uninstalling");
472  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
473  ASSERT_EQ(crash_size_before,
474            GetExtensionService()->terminated_extensions()->size());
475  ASSERT_EQ(0U, CountBalloons());
476}
477
478IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, CrashAndUnloadAll) {
479  const size_t size_before = GetExtensionService()->extensions()->size();
480  const size_t crash_size_before =
481      GetExtensionService()->terminated_extensions()->size();
482  LoadTestExtension();
483  LoadSecondExtension();
484  CrashExtension(size_before);
485  ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size());
486  ASSERT_EQ(crash_size_before + 1,
487            GetExtensionService()->terminated_extensions()->size());
488
489  GetExtensionService()->UnloadAllExtensions();
490  ASSERT_EQ(crash_size_before,
491            GetExtensionService()->terminated_extensions()->size());
492}
493