1// Copyright 2014 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/message_loop/message_loop_proxy.h" 6#include "chrome/browser/apps/ephemeral_app_launcher.h" 7#include "chrome/browser/apps/ephemeral_app_service.h" 8#include "chrome/browser/extensions/extension_install_checker.h" 9#include "chrome/browser/extensions/extension_service.h" 10#include "chrome/browser/extensions/install_tracker.h" 11#include "chrome/browser/extensions/test_blacklist.h" 12#include "chrome/browser/extensions/webstore_installer_test.h" 13#include "chrome/browser/ui/browser_finder.h" 14#include "chrome/browser/ui/tabs/tab_strip_model.h" 15#include "chrome/common/chrome_switches.h" 16#include "content/public/browser/web_contents.h" 17#include "content/public/test/test_utils.h" 18#include "extensions/browser/extension_prefs.h" 19#include "extensions/browser/extension_registry.h" 20#include "extensions/browser/extension_system.h" 21#include "extensions/browser/extension_util.h" 22#include "extensions/browser/management_policy.h" 23#include "extensions/browser/process_manager.h" 24#include "extensions/common/switches.h" 25#include "extensions/test/extension_test_message_listener.h" 26 27using extensions::Extension; 28using extensions::ExtensionPrefs; 29using extensions::ExtensionRegistry; 30using extensions::ExtensionSystem; 31using extensions::InstallTracker; 32namespace webstore_install = extensions::webstore_install; 33 34namespace { 35 36const char kWebstoreDomain[] = "cws.com"; 37const char kAppDomain[] = "app.com"; 38const char kNonAppDomain[] = "nonapp.com"; 39const char kTestDataPath[] = "extensions/platform_apps/ephemeral_launcher"; 40 41const char kExtensionId[] = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeid"; 42const char kExtensionTestPath[] = "extension"; 43const char kLegacyAppId[] = "lnbochkobjfnhbnbljgfgokadhmbahcn"; 44const char kLegacyAppTestPath[] = "legacy_app"; 45const char kNonExistentId[] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaadid"; 46const char kDefaultAppId[] = "kbiancnbopdghkfedjhfdoegjadfjeal"; 47const char kDefaultAppCrxFilename[] = "app.crx"; 48const char kDefaultAppTestPath[] = "app"; 49const char kAppWithPermissionsId[] = "mbfcnecjknjpipkfkoangpfnhhlpamki"; 50const char kAppWithPermissionsFilename[] = "app_with_permissions.crx"; 51const char kHostedAppId[] = "haaaaaaaaaaaaaaaaaaaaaaaaaaappid"; 52const char kHostedAppLaunchUrl[] = "http://foo.bar.com"; 53 54class ExtensionInstallCheckerMock : public extensions::ExtensionInstallChecker { 55 public: 56 ExtensionInstallCheckerMock(Profile* profile, 57 const std::string& requirements_error) 58 : extensions::ExtensionInstallChecker(profile), 59 requirements_error_(requirements_error) {} 60 61 virtual ~ExtensionInstallCheckerMock() {} 62 63 private: 64 virtual void CheckRequirements() OVERRIDE { 65 // Simulate an asynchronous operation. 66 base::MessageLoopProxy::current()->PostTask( 67 FROM_HERE, 68 base::Bind(&ExtensionInstallCheckerMock::RequirementsErrorCheckDone, 69 base::Unretained(this), 70 current_sequence_number())); 71 } 72 73 void RequirementsErrorCheckDone(int sequence_number) { 74 std::vector<std::string> errors; 75 errors.push_back(requirements_error_); 76 OnRequirementsCheckDone(sequence_number, errors); 77 } 78 79 std::string requirements_error_; 80}; 81 82class EphemeralAppLauncherForTest : public EphemeralAppLauncher { 83 public: 84 EphemeralAppLauncherForTest(const std::string& id, 85 Profile* profile, 86 const LaunchCallback& callback) 87 : EphemeralAppLauncher(id, profile, NULL, callback), 88 install_initiated_(false), 89 install_prompt_created_(false) {} 90 91 EphemeralAppLauncherForTest(const std::string& id, Profile* profile) 92 : EphemeralAppLauncher(id, profile, NULL, LaunchCallback()), 93 install_initiated_(false), 94 install_prompt_created_(false) {} 95 96 bool install_initiated() const { return install_initiated_; } 97 bool install_prompt_created() const { return install_prompt_created_; } 98 99 void set_requirements_error(const std::string& error) { 100 requirements_check_error_ = error; 101 } 102 103 private: 104 // Override necessary functions for testing. 105 106 virtual scoped_ptr<extensions::ExtensionInstallChecker> CreateInstallChecker() 107 OVERRIDE { 108 if (requirements_check_error_.empty()) { 109 return EphemeralAppLauncher::CreateInstallChecker(); 110 } else { 111 return scoped_ptr<extensions::ExtensionInstallChecker>( 112 new ExtensionInstallCheckerMock(profile(), 113 requirements_check_error_)); 114 } 115 } 116 117 virtual scoped_ptr<ExtensionInstallPrompt> CreateInstallUI() OVERRIDE { 118 install_prompt_created_ = true; 119 return EphemeralAppLauncher::CreateInstallUI(); 120 } 121 122 virtual scoped_ptr<extensions::WebstoreInstaller::Approval> CreateApproval() 123 const OVERRIDE { 124 install_initiated_ = true; 125 return EphemeralAppLauncher::CreateApproval(); 126 } 127 128 private: 129 virtual ~EphemeralAppLauncherForTest() {} 130 friend class base::RefCountedThreadSafe<EphemeralAppLauncherForTest>; 131 132 mutable bool install_initiated_; 133 std::string requirements_check_error_; 134 bool install_prompt_created_; 135}; 136 137class LaunchObserver { 138 public: 139 LaunchObserver() 140 : done_(false), 141 waiting_(false), 142 result_(webstore_install::OTHER_ERROR) {} 143 144 webstore_install::Result result() const { return result_; } 145 const std::string& error() const { return error_; } 146 147 void OnLaunchCallback(webstore_install::Result result, 148 const std::string& error) { 149 result_ = result; 150 error_ = error; 151 done_ = true; 152 if (waiting_) { 153 waiting_ = false; 154 base::MessageLoopForUI::current()->Quit(); 155 } 156 } 157 158 void Wait() { 159 if (done_) 160 return; 161 162 waiting_ = true; 163 content::RunMessageLoop(); 164 } 165 166 private: 167 bool done_; 168 bool waiting_; 169 webstore_install::Result result_; 170 std::string error_; 171}; 172 173class ManagementPolicyMock : public extensions::ManagementPolicy::Provider { 174 public: 175 ManagementPolicyMock() {} 176 177 virtual std::string GetDebugPolicyProviderName() const OVERRIDE { 178 return "ManagementPolicyMock"; 179 } 180 181 virtual bool UserMayLoad(const Extension* extension, 182 base::string16* error) const OVERRIDE { 183 return false; 184 } 185}; 186 187} // namespace 188 189class EphemeralAppLauncherTest : public WebstoreInstallerTest { 190 public: 191 EphemeralAppLauncherTest() 192 : WebstoreInstallerTest(kWebstoreDomain, 193 kTestDataPath, 194 kDefaultAppCrxFilename, 195 kAppDomain, 196 kNonAppDomain) {} 197 198 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE { 199 WebstoreInstallerTest::SetUpCommandLine(command_line); 200 201 // Make event pages get suspended immediately. 202 extensions::ProcessManager::SetEventPageIdleTimeForTesting(1); 203 extensions::ProcessManager::SetEventPageSuspendingTimeForTesting(1); 204 205 // Enable ephemeral apps flag. 206 command_line->AppendSwitch(switches::kEnableEphemeralApps); 207 } 208 209 virtual void SetUpOnMainThread() OVERRIDE { 210 WebstoreInstallerTest::SetUpOnMainThread(); 211 212 // Disable ephemeral apps immediately after they stop running in tests. 213 EphemeralAppService::Get(profile())->set_disable_delay_for_test(0); 214 } 215 216 base::FilePath GetTestPath(const char* test_name) { 217 return test_data_dir_.AppendASCII("platform_apps/ephemeral_launcher") 218 .AppendASCII(test_name); 219 } 220 221 const Extension* GetInstalledExtension(const std::string& id) { 222 return ExtensionRegistry::Get(profile()) 223 ->GetExtensionById(id, ExtensionRegistry::EVERYTHING); 224 } 225 226 void SetCrxFilename(const std::string& filename) { 227 GURL crx_url = GenerateTestServerUrl(kWebstoreDomain, filename); 228 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( 229 switches::kAppsGalleryUpdateURL, crx_url.spec()); 230 } 231 232 void StartLauncherAndCheckResult(EphemeralAppLauncherForTest* launcher, 233 webstore_install::Result expected_result, 234 bool expect_install_initiated) { 235 ExtensionTestMessageListener launched_listener("launched", false); 236 LaunchObserver launch_observer; 237 238 launcher->launch_callback_ = base::Bind(&LaunchObserver::OnLaunchCallback, 239 base::Unretained(&launch_observer)); 240 launcher->Start(); 241 launch_observer.Wait(); 242 243 // Verify the launch result. 244 EXPECT_EQ(expected_result, launch_observer.result()); 245 EXPECT_EQ(expect_install_initiated, launcher->install_initiated()); 246 247 // Verify that the app was actually launched if the launcher succeeded. 248 if (launch_observer.result() == webstore_install::SUCCESS) 249 EXPECT_TRUE(launched_listener.WaitUntilSatisfied()); 250 else 251 EXPECT_FALSE(launched_listener.was_satisfied()); 252 253 // Check the reference count to ensure the launcher instance will not be 254 // leaked. 255 EXPECT_TRUE(launcher->HasOneRef()); 256 } 257 258 void RunLaunchTest(const std::string& id, 259 webstore_install::Result expected_result, 260 bool expect_install_initiated) { 261 InstallTracker* tracker = InstallTracker::Get(profile()); 262 ASSERT_TRUE(tracker); 263 bool was_install_active = !!tracker->GetActiveInstall(id); 264 265 scoped_refptr<EphemeralAppLauncherForTest> launcher( 266 new EphemeralAppLauncherForTest(id, profile())); 267 StartLauncherAndCheckResult( 268 launcher.get(), expected_result, expect_install_initiated); 269 270 // Verify that the install was deregistered from the InstallTracker. 271 EXPECT_EQ(was_install_active, !!tracker->GetActiveInstall(id)); 272 } 273 274 void ValidateAppInstalledEphemerally(const std::string& id) { 275 EXPECT_TRUE(GetInstalledExtension(id)); 276 EXPECT_TRUE(extensions::util::IsEphemeralApp(id, profile())); 277 } 278 279 const Extension* InstallAndDisableApp( 280 const char* test_path, 281 Extension::DisableReason disable_reason) { 282 const Extension* app = InstallExtension(GetTestPath(test_path), 1); 283 EXPECT_TRUE(app); 284 if (!app) 285 return NULL; 286 287 ExtensionService* service = 288 ExtensionSystem::Get(profile())->extension_service(); 289 service->DisableExtension(app->id(), disable_reason); 290 291 if (disable_reason == Extension::DISABLE_PERMISSIONS_INCREASE) { 292 // When an extension is disabled due to a permissions increase, this 293 // flag needs to be set too, for some reason. 294 ExtensionPrefs::Get(profile()) 295 ->SetDidExtensionEscalatePermissions(app, true); 296 } 297 298 EXPECT_TRUE( 299 ExtensionRegistry::Get(profile())->disabled_extensions().Contains( 300 app->id())); 301 return app; 302 } 303}; 304 305class EphemeralAppLauncherTestDisabled : public EphemeralAppLauncherTest { 306 public: 307 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE { 308 // Skip EphemeralAppLauncherTest as it enables the feature. 309 WebstoreInstallerTest::SetUpCommandLine(command_line); 310 } 311}; 312 313// Verifies that an ephemeral app will not be installed and launched if the 314// feature is disabled. 315IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTestDisabled, FeatureDisabled) { 316 RunLaunchTest( 317 kDefaultAppCrxFilename, webstore_install::LAUNCH_FEATURE_DISABLED, false); 318 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); 319} 320 321// Verifies that an app with no permission warnings will be installed 322// ephemerally and launched without prompting the user. 323IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, 324 LaunchAppWithNoPermissionWarnings) { 325 content::WindowedNotificationObserver unloaded_signal( 326 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, 327 content::Source<Profile>(profile())); 328 329 scoped_refptr<EphemeralAppLauncherForTest> launcher( 330 new EphemeralAppLauncherForTest(kDefaultAppId, profile())); 331 StartLauncherAndCheckResult(launcher.get(), webstore_install::SUCCESS, true); 332 ValidateAppInstalledEphemerally(kDefaultAppId); 333 334 // Apps with no permission warnings should not result in a prompt. 335 EXPECT_FALSE(launcher->install_prompt_created()); 336 337 // Ephemeral apps are unloaded after they stop running. 338 unloaded_signal.Wait(); 339 340 // After an app has been installed ephemerally, it can be launched again 341 // without installing from the web store. 342 RunLaunchTest(kDefaultAppId, webstore_install::SUCCESS, false); 343} 344 345// Verifies that an app with permission warnings will be installed 346// ephemerally and launched if accepted by the user. 347IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, 348 LaunchAppWithPermissionsWarnings) { 349 SetCrxFilename(kAppWithPermissionsFilename); 350 AutoAcceptInstall(); 351 352 scoped_refptr<EphemeralAppLauncherForTest> launcher( 353 new EphemeralAppLauncherForTest(kAppWithPermissionsId, profile())); 354 StartLauncherAndCheckResult(launcher.get(), webstore_install::SUCCESS, true); 355 ValidateAppInstalledEphemerally(kAppWithPermissionsId); 356 EXPECT_TRUE(launcher->install_prompt_created()); 357} 358 359// Verifies that an app with permission warnings will not be installed 360// ephemerally if cancelled by the user. 361IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, 362 CancelInstallAppWithPermissionWarnings) { 363 SetCrxFilename(kAppWithPermissionsFilename); 364 AutoCancelInstall(); 365 366 scoped_refptr<EphemeralAppLauncherForTest> launcher( 367 new EphemeralAppLauncherForTest(kAppWithPermissionsId, profile())); 368 StartLauncherAndCheckResult( 369 launcher.get(), webstore_install::USER_CANCELLED, false); 370 EXPECT_FALSE(GetInstalledExtension(kAppWithPermissionsId)); 371 EXPECT_TRUE(launcher->install_prompt_created()); 372} 373 374// Verifies that an extension will not be installed ephemerally. 375IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, InstallExtension) { 376 RunLaunchTest( 377 kExtensionId, webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, false); 378 EXPECT_FALSE(GetInstalledExtension(kExtensionId)); 379} 380 381// Verifies that an already installed extension will not be launched. 382IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchExtension) { 383 const Extension* extension = 384 InstallExtension(GetTestPath(kExtensionTestPath), 1); 385 ASSERT_TRUE(extension); 386 RunLaunchTest(extension->id(), 387 webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, 388 false); 389} 390 391// Verifies that a legacy packaged app will not be installed ephemerally. 392IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, InstallLegacyApp) { 393 RunLaunchTest( 394 kLegacyAppId, webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, false); 395 EXPECT_FALSE(GetInstalledExtension(kLegacyAppId)); 396} 397 398// Verifies that a legacy packaged app that is already installed can be 399// launched. 400IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchLegacyApp) { 401 const Extension* extension = 402 InstallExtension(GetTestPath(kLegacyAppTestPath), 1); 403 ASSERT_TRUE(extension); 404 RunLaunchTest(extension->id(), webstore_install::SUCCESS, false); 405} 406 407// Verifies that a hosted app is not installed. Launch succeeds because we 408// navigate to its launch url. 409IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchHostedApp) { 410 LaunchObserver launch_observer; 411 412 scoped_refptr<EphemeralAppLauncherForTest> launcher( 413 new EphemeralAppLauncherForTest( 414 kHostedAppId, 415 profile(), 416 base::Bind(&LaunchObserver::OnLaunchCallback, 417 base::Unretained(&launch_observer)))); 418 launcher->Start(); 419 launch_observer.Wait(); 420 421 EXPECT_EQ(webstore_install::SUCCESS, launch_observer.result()); 422 EXPECT_FALSE(launcher->install_initiated()); 423 EXPECT_FALSE(GetInstalledExtension(kHostedAppId)); 424 425 // Verify that a navigation to the launch url was attempted. 426 Browser* browser = 427 FindBrowserWithProfile(profile(), chrome::GetActiveDesktop()); 428 ASSERT_TRUE(browser); 429 content::WebContents* web_contents = 430 browser->tab_strip_model()->GetActiveWebContents(); 431 ASSERT_TRUE(web_contents); 432 EXPECT_EQ(GURL(kHostedAppLaunchUrl), web_contents->GetVisibleURL()); 433} 434 435// Verifies that the EphemeralAppLauncher handles non-existent extension ids. 436IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, NonExistentExtensionId) { 437 RunLaunchTest( 438 kNonExistentId, webstore_install::WEBSTORE_REQUEST_ERROR, false); 439 EXPECT_FALSE(GetInstalledExtension(kNonExistentId)); 440} 441 442// Verifies that an app blocked by management policy is not installed 443// ephemerally. 444IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlockedByPolicy) { 445 // Register a provider that blocks the installation of all apps. 446 ManagementPolicyMock policy; 447 ExtensionSystem::Get(profile())->management_policy()->RegisterProvider( 448 &policy); 449 450 RunLaunchTest(kDefaultAppId, webstore_install::BLOCKED_BY_POLICY, false); 451 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); 452} 453 454// Verifies that an app blacklisted for malware is not installed ephemerally. 455IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlacklistedForMalware) { 456 // Mock a BLACKLISTED_MALWARE return status. 457 extensions::TestBlacklist blacklist_tester( 458 ExtensionSystem::Get(profile())->blacklist()); 459 blacklist_tester.SetBlacklistState( 460 kDefaultAppId, extensions::BLACKLISTED_MALWARE, false); 461 462 RunLaunchTest(kDefaultAppId, webstore_install::BLACKLISTED, false); 463 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); 464} 465 466// Verifies that an app with unknown blacklist status is installed ephemerally 467// and launched. 468IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlacklistStateUnknown) { 469 // Mock a BLACKLISTED_MALWARE return status. 470 extensions::TestBlacklist blacklist_tester( 471 ExtensionSystem::Get(profile())->blacklist()); 472 blacklist_tester.SetBlacklistState( 473 kDefaultAppId, extensions::BLACKLISTED_UNKNOWN, false); 474 475 RunLaunchTest(kDefaultAppId, webstore_install::SUCCESS, true); 476 ValidateAppInstalledEphemerally(kDefaultAppId); 477} 478 479// Verifies that an app with unsupported requirements is not installed 480// ephemerally. 481IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, UnsupportedRequirements) { 482 scoped_refptr<EphemeralAppLauncherForTest> launcher( 483 new EphemeralAppLauncherForTest(kDefaultAppId, profile())); 484 launcher->set_requirements_error("App has unsupported requirements"); 485 486 StartLauncherAndCheckResult( 487 launcher.get(), webstore_install::REQUIREMENT_VIOLATIONS, false); 488 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); 489} 490 491// Verifies that an app disabled due to permissions increase can be enabled 492// and launched. 493IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, EnableAndLaunchApp) { 494 const Extension* app = InstallAndDisableApp( 495 kDefaultAppTestPath, Extension::DISABLE_PERMISSIONS_INCREASE); 496 ASSERT_TRUE(app); 497 498 AutoAcceptInstall(); 499 RunLaunchTest(app->id(), webstore_install::SUCCESS, false); 500} 501 502// Verifies that if the user cancels the enable flow, the app will not be 503// enabled and launched. 504IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, EnableCancelled) { 505 const Extension* app = InstallAndDisableApp( 506 kDefaultAppTestPath, Extension::DISABLE_PERMISSIONS_INCREASE); 507 ASSERT_TRUE(app); 508 509 AutoCancelInstall(); 510 RunLaunchTest(app->id(), webstore_install::USER_CANCELLED, false); 511} 512 513// Verifies that an installed app that had been blocked by policy cannot be 514// launched. 515IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchAppBlockedByPolicy) { 516 const Extension* app = InstallExtension(GetTestPath(kDefaultAppTestPath), 1); 517 ASSERT_TRUE(app); 518 519 // Simulate blocking of the app after it has been installed. 520 ManagementPolicyMock policy; 521 ExtensionSystem::Get(profile())->management_policy()->RegisterProvider( 522 &policy); 523 ExtensionSystem::Get(profile())->extension_service()->CheckManagementPolicy(); 524 525 RunLaunchTest(app->id(), webstore_install::BLOCKED_BY_POLICY, false); 526} 527 528// Verifies that an installed blacklisted app cannot be launched. 529IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchBlacklistedApp) { 530 const Extension* app = InstallExtension(GetTestPath(kDefaultAppTestPath), 1); 531 ASSERT_TRUE(app); 532 533 ExtensionService* service = 534 ExtensionSystem::Get(profile())->extension_service(); 535 service->BlacklistExtensionForTest(app->id()); 536 ASSERT_TRUE( 537 ExtensionRegistry::Get(profile())->blacklisted_extensions().Contains( 538 app->id())); 539 540 RunLaunchTest(app->id(), webstore_install::BLACKLISTED, false); 541} 542 543// Verifies that an installed app with unsupported requirements cannot be 544// launched. 545IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, 546 LaunchAppWithUnsupportedRequirements) { 547 const Extension* app = InstallAndDisableApp( 548 kDefaultAppTestPath, Extension::DISABLE_UNSUPPORTED_REQUIREMENT); 549 ASSERT_TRUE(app); 550 551 RunLaunchTest(app->id(), webstore_install::REQUIREMENT_VIOLATIONS, false); 552} 553 554// Verifies that a launch will fail if the app is currently being installed. 555IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, InstallInProgress) { 556 extensions::ActiveInstallData install_data(kDefaultAppId); 557 InstallTracker::Get(profile())->AddActiveInstall(install_data); 558 559 RunLaunchTest(kDefaultAppId, webstore_install::INSTALL_IN_PROGRESS, false); 560} 561 562// Verifies that a launch will fail if a duplicate launch is in progress. 563IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, DuplicateLaunchInProgress) { 564 extensions::ActiveInstallData install_data(kDefaultAppId); 565 install_data.is_ephemeral = true; 566 InstallTracker::Get(profile())->AddActiveInstall(install_data); 567 568 RunLaunchTest(kDefaultAppId, webstore_install::LAUNCH_IN_PROGRESS, false); 569} 570