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 24 class 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 122 IN_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 139 IN_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 159 IN_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 178 IN_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 206 IN_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 233 IN_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 263 IN_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. 307 IN_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 314 IN_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 327 IN_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 340 IN_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 370 IN_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. 396 IN_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 407 IN_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 426 IN_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 456 IN_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 478 IN_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