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.
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/extension_test_message_listener.h"
11 #include "chrome/browser/extensions/install_tracker.h"
12 #include "chrome/browser/extensions/test_blacklist.h"
13 #include "chrome/browser/extensions/webstore_installer_test.h"
14 #include "chrome/browser/ui/browser_finder.h"
15 #include "chrome/browser/ui/tabs/tab_strip_model.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/test/test_utils.h"
19 #include "extensions/browser/extension_prefs.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/browser/extension_util.h"
23 #include "extensions/browser/management_policy.h"
24 #include "extensions/common/switches.h"
26 using extensions::Extension
;
27 using extensions::ExtensionPrefs
;
28 using extensions::ExtensionRegistry
;
29 using extensions::ExtensionSystem
;
30 using extensions::InstallTracker
;
31 namespace webstore_install
= extensions::webstore_install
;
35 const char kWebstoreDomain
[] = "cws.com";
36 const char kAppDomain
[] = "app.com";
37 const char kNonAppDomain
[] = "nonapp.com";
38 const char kTestDataPath
[] = "extensions/platform_apps/ephemeral_launcher";
40 const char kExtensionId
[] = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeid";
41 const char kExtensionTestPath
[] = "extension";
42 const char kLegacyAppId
[] = "lnbochkobjfnhbnbljgfgokadhmbahcn";
43 const char kLegacyAppTestPath
[] = "legacy_app";
44 const char kNonExistentId
[] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaadid";
45 const char kDefaultAppId
[] = "kbiancnbopdghkfedjhfdoegjadfjeal";
46 const char kDefaultAppCrxFilename
[] = "app.crx";
47 const char kDefaultAppTestPath
[] = "app";
48 const char kAppWithPermissionsId
[] = "mbfcnecjknjpipkfkoangpfnhhlpamki";
49 const char kAppWithPermissionsFilename
[] = "app_with_permissions.crx";
50 const char kHostedAppId
[] = "haaaaaaaaaaaaaaaaaaaaaaaaaaappid";
51 const char kHostedAppLaunchUrl
[] = "http://foo.bar.com";
53 class ExtensionInstallCheckerMock
: public extensions::ExtensionInstallChecker
{
55 ExtensionInstallCheckerMock(Profile
* profile
,
56 const std::string
& requirements_error
)
57 : extensions::ExtensionInstallChecker(profile
),
58 requirements_error_(requirements_error
) {}
60 virtual ~ExtensionInstallCheckerMock() {}
63 virtual void CheckRequirements() OVERRIDE
{
64 // Simulate an asynchronous operation.
65 base::MessageLoopProxy::current()->PostTask(
67 base::Bind(&ExtensionInstallCheckerMock::RequirementsErrorCheckDone
,
68 base::Unretained(this),
69 current_sequence_number()));
72 void RequirementsErrorCheckDone(int sequence_number
) {
73 std::vector
<std::string
> errors
;
74 errors
.push_back(requirements_error_
);
75 OnRequirementsCheckDone(sequence_number
, errors
);
78 std::string requirements_error_
;
81 class EphemeralAppLauncherForTest
: public EphemeralAppLauncher
{
83 EphemeralAppLauncherForTest(const std::string
& id
,
85 const LaunchCallback
& callback
)
86 : EphemeralAppLauncher(id
, profile
, NULL
, callback
),
87 install_initiated_(false),
88 install_prompt_created_(false) {}
90 EphemeralAppLauncherForTest(const std::string
& id
, Profile
* profile
)
91 : EphemeralAppLauncher(id
, profile
, NULL
, LaunchCallback()),
92 install_initiated_(false),
93 install_prompt_created_(false) {}
95 bool install_initiated() const { return install_initiated_
; }
96 bool install_prompt_created() const { return install_prompt_created_
; }
98 void set_requirements_error(const std::string
& error
) {
99 requirements_check_error_
= error
;
103 // Override necessary functions for testing.
105 virtual scoped_ptr
<extensions::ExtensionInstallChecker
> CreateInstallChecker()
107 if (requirements_check_error_
.empty()) {
108 return EphemeralAppLauncher::CreateInstallChecker();
110 return scoped_ptr
<extensions::ExtensionInstallChecker
>(
111 new ExtensionInstallCheckerMock(profile(),
112 requirements_check_error_
));
116 virtual scoped_ptr
<ExtensionInstallPrompt
> CreateInstallUI() OVERRIDE
{
117 install_prompt_created_
= true;
118 return EphemeralAppLauncher::CreateInstallUI();
121 virtual scoped_ptr
<extensions::WebstoreInstaller::Approval
> CreateApproval()
123 install_initiated_
= true;
124 return EphemeralAppLauncher::CreateApproval();
128 virtual ~EphemeralAppLauncherForTest() {}
129 friend class base::RefCountedThreadSafe
<EphemeralAppLauncherForTest
>;
131 mutable bool install_initiated_
;
132 std::string requirements_check_error_
;
133 bool install_prompt_created_
;
136 class LaunchObserver
{
141 result_(webstore_install::OTHER_ERROR
) {}
143 webstore_install::Result
result() const { return result_
; }
144 const std::string
& error() const { return error_
; }
146 void OnLaunchCallback(webstore_install::Result result
,
147 const std::string
& error
) {
153 base::MessageLoopForUI::current()->Quit();
162 content::RunMessageLoop();
168 webstore_install::Result result_
;
172 class ManagementPolicyMock
: public extensions::ManagementPolicy::Provider
{
174 ManagementPolicyMock() {}
176 virtual std::string
GetDebugPolicyProviderName() const OVERRIDE
{
177 return "ManagementPolicyMock";
180 virtual bool UserMayLoad(const Extension
* extension
,
181 base::string16
* error
) const OVERRIDE
{
188 class EphemeralAppLauncherTest
: public WebstoreInstallerTest
{
190 EphemeralAppLauncherTest()
191 : WebstoreInstallerTest(kWebstoreDomain
,
193 kDefaultAppCrxFilename
,
197 virtual void SetUpCommandLine(base::CommandLine
* command_line
) OVERRIDE
{
198 WebstoreInstallerTest::SetUpCommandLine(command_line
);
200 // Make event pages get suspended immediately.
201 command_line
->AppendSwitchASCII(extensions::switches::kEventPageIdleTime
,
203 command_line
->AppendSwitchASCII(
204 extensions::switches::kEventPageSuspendingTime
, "10");
206 // Enable ephemeral apps flag.
207 command_line
->AppendSwitch(switches::kEnableEphemeralApps
);
210 virtual void SetUpOnMainThread() OVERRIDE
{
211 WebstoreInstallerTest::SetUpOnMainThread();
213 // Disable ephemeral apps immediately after they stop running in tests.
214 EphemeralAppService::Get(profile())->set_disable_delay_for_test(0);
217 base::FilePath
GetTestPath(const char* test_name
) {
218 return test_data_dir_
.AppendASCII("platform_apps/ephemeral_launcher")
219 .AppendASCII(test_name
);
222 const Extension
* GetInstalledExtension(const std::string
& id
) {
223 return ExtensionRegistry::Get(profile())
224 ->GetExtensionById(id
, ExtensionRegistry::EVERYTHING
);
227 void SetCrxFilename(const std::string
& filename
) {
228 GURL crx_url
= GenerateTestServerUrl(kWebstoreDomain
, filename
);
229 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
230 switches::kAppsGalleryUpdateURL
, crx_url
.spec());
233 void StartLauncherAndCheckResult(EphemeralAppLauncherForTest
* launcher
,
234 webstore_install::Result expected_result
,
235 bool expect_install_initiated
) {
236 ExtensionTestMessageListener
launched_listener("launched", false);
237 LaunchObserver launch_observer
;
239 launcher
->launch_callback_
= base::Bind(&LaunchObserver::OnLaunchCallback
,
240 base::Unretained(&launch_observer
));
242 launch_observer
.Wait();
244 // Verify the launch result.
245 EXPECT_EQ(expected_result
, launch_observer
.result());
246 EXPECT_EQ(expect_install_initiated
, launcher
->install_initiated());
248 // Verify that the app was actually launched if the launcher succeeded.
249 if (launch_observer
.result() == webstore_install::SUCCESS
)
250 EXPECT_TRUE(launched_listener
.WaitUntilSatisfied());
252 EXPECT_FALSE(launched_listener
.was_satisfied());
254 // Check the reference count to ensure the launcher instance will not be
256 EXPECT_TRUE(launcher
->HasOneRef());
259 void RunLaunchTest(const std::string
& id
,
260 webstore_install::Result expected_result
,
261 bool expect_install_initiated
) {
262 InstallTracker
* tracker
= InstallTracker::Get(profile());
263 ASSERT_TRUE(tracker
);
264 bool was_install_active
= !!tracker
->GetActiveInstall(id
);
266 scoped_refptr
<EphemeralAppLauncherForTest
> launcher(
267 new EphemeralAppLauncherForTest(id
, profile()));
268 StartLauncherAndCheckResult(
269 launcher
.get(), expected_result
, expect_install_initiated
);
271 // Verify that the install was deregistered from the InstallTracker.
272 EXPECT_EQ(was_install_active
, !!tracker
->GetActiveInstall(id
));
275 void ValidateAppInstalledEphemerally(const std::string
& id
) {
276 EXPECT_TRUE(GetInstalledExtension(id
));
277 EXPECT_TRUE(extensions::util::IsEphemeralApp(id
, profile()));
280 const Extension
* InstallAndDisableApp(
281 const char* test_path
,
282 Extension::DisableReason disable_reason
) {
283 const Extension
* app
= InstallExtension(GetTestPath(test_path
), 1);
288 ExtensionService
* service
=
289 ExtensionSystem::Get(profile())->extension_service();
290 service
->DisableExtension(app
->id(), disable_reason
);
292 if (disable_reason
== Extension::DISABLE_PERMISSIONS_INCREASE
) {
293 // When an extension is disabled due to a permissions increase, this
294 // flag needs to be set too, for some reason.
295 ExtensionPrefs::Get(profile())
296 ->SetDidExtensionEscalatePermissions(app
, true);
300 ExtensionRegistry::Get(profile())->disabled_extensions().Contains(
306 class EphemeralAppLauncherTestDisabled
: public EphemeralAppLauncherTest
{
308 virtual void SetUpCommandLine(base::CommandLine
* command_line
) OVERRIDE
{
309 // Skip EphemeralAppLauncherTest as it enables the feature.
310 WebstoreInstallerTest::SetUpCommandLine(command_line
);
314 // Verifies that an ephemeral app will not be installed and launched if the
315 // feature is disabled.
316 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTestDisabled
, FeatureDisabled
) {
318 kDefaultAppCrxFilename
, webstore_install::LAUNCH_FEATURE_DISABLED
, false);
319 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId
));
322 // Verifies that an app with no permission warnings will be installed
323 // ephemerally and launched without prompting the user.
324 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
,
325 LaunchAppWithNoPermissionWarnings
) {
326 content::WindowedNotificationObserver
unloaded_signal(
327 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
,
328 content::Source
<Profile
>(profile()));
330 scoped_refptr
<EphemeralAppLauncherForTest
> launcher(
331 new EphemeralAppLauncherForTest(kDefaultAppId
, profile()));
332 StartLauncherAndCheckResult(launcher
.get(), webstore_install::SUCCESS
, true);
333 ValidateAppInstalledEphemerally(kDefaultAppId
);
335 // Apps with no permission warnings should not result in a prompt.
336 EXPECT_FALSE(launcher
->install_prompt_created());
338 // Ephemeral apps are unloaded after they stop running.
339 unloaded_signal
.Wait();
341 // After an app has been installed ephemerally, it can be launched again
342 // without installing from the web store.
343 RunLaunchTest(kDefaultAppId
, webstore_install::SUCCESS
, false);
346 // Verifies that an app with permission warnings will be installed
347 // ephemerally and launched if accepted by the user.
348 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
,
349 LaunchAppWithPermissionsWarnings
) {
350 SetCrxFilename(kAppWithPermissionsFilename
);
353 scoped_refptr
<EphemeralAppLauncherForTest
> launcher(
354 new EphemeralAppLauncherForTest(kAppWithPermissionsId
, profile()));
355 StartLauncherAndCheckResult(launcher
.get(), webstore_install::SUCCESS
, true);
356 ValidateAppInstalledEphemerally(kAppWithPermissionsId
);
357 EXPECT_TRUE(launcher
->install_prompt_created());
360 // Verifies that an app with permission warnings will not be installed
361 // ephemerally if cancelled by the user.
362 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
,
363 CancelInstallAppWithPermissionWarnings
) {
364 SetCrxFilename(kAppWithPermissionsFilename
);
367 scoped_refptr
<EphemeralAppLauncherForTest
> launcher(
368 new EphemeralAppLauncherForTest(kAppWithPermissionsId
, profile()));
369 StartLauncherAndCheckResult(
370 launcher
.get(), webstore_install::USER_CANCELLED
, false);
371 EXPECT_FALSE(GetInstalledExtension(kAppWithPermissionsId
));
372 EXPECT_TRUE(launcher
->install_prompt_created());
375 // Verifies that an extension will not be installed ephemerally.
376 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, InstallExtension
) {
378 kExtensionId
, webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE
, false);
379 EXPECT_FALSE(GetInstalledExtension(kExtensionId
));
382 // Verifies that an already installed extension will not be launched.
383 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, LaunchExtension
) {
384 const Extension
* extension
=
385 InstallExtension(GetTestPath(kExtensionTestPath
), 1);
386 ASSERT_TRUE(extension
);
387 RunLaunchTest(extension
->id(),
388 webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE
,
392 // Verifies that a legacy packaged app will not be installed ephemerally.
393 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, InstallLegacyApp
) {
395 kLegacyAppId
, webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE
, false);
396 EXPECT_FALSE(GetInstalledExtension(kLegacyAppId
));
399 // Verifies that a legacy packaged app that is already installed can be
401 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, LaunchLegacyApp
) {
402 const Extension
* extension
=
403 InstallExtension(GetTestPath(kLegacyAppTestPath
), 1);
404 ASSERT_TRUE(extension
);
405 RunLaunchTest(extension
->id(), webstore_install::SUCCESS
, false);
408 // Verifies that a hosted app is not installed. Launch succeeds because we
409 // navigate to its launch url.
410 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, LaunchHostedApp
) {
411 LaunchObserver launch_observer
;
413 scoped_refptr
<EphemeralAppLauncherForTest
> launcher(
414 new EphemeralAppLauncherForTest(
417 base::Bind(&LaunchObserver::OnLaunchCallback
,
418 base::Unretained(&launch_observer
))));
420 launch_observer
.Wait();
422 EXPECT_EQ(webstore_install::SUCCESS
, launch_observer
.result());
423 EXPECT_FALSE(launcher
->install_initiated());
424 EXPECT_FALSE(GetInstalledExtension(kHostedAppId
));
426 // Verify that a navigation to the launch url was attempted.
428 FindBrowserWithProfile(profile(), chrome::GetActiveDesktop());
429 ASSERT_TRUE(browser
);
430 content::WebContents
* web_contents
=
431 browser
->tab_strip_model()->GetActiveWebContents();
432 ASSERT_TRUE(web_contents
);
433 EXPECT_EQ(GURL(kHostedAppLaunchUrl
), web_contents
->GetVisibleURL());
436 // Verifies that the EphemeralAppLauncher handles non-existent extension ids.
437 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, NonExistentExtensionId
) {
439 kNonExistentId
, webstore_install::WEBSTORE_REQUEST_ERROR
, false);
440 EXPECT_FALSE(GetInstalledExtension(kNonExistentId
));
443 // Verifies that an app blocked by management policy is not installed
445 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, BlockedByPolicy
) {
446 // Register a provider that blocks the installation of all apps.
447 ManagementPolicyMock policy
;
448 ExtensionSystem::Get(profile())->management_policy()->RegisterProvider(
451 RunLaunchTest(kDefaultAppId
, webstore_install::BLOCKED_BY_POLICY
, false);
452 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId
));
455 // Verifies that an app blacklisted for malware is not installed ephemerally.
456 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, BlacklistedForMalware
) {
457 // Mock a BLACKLISTED_MALWARE return status.
458 extensions::TestBlacklist
blacklist_tester(
459 ExtensionSystem::Get(profile())->blacklist());
460 blacklist_tester
.SetBlacklistState(
461 kDefaultAppId
, extensions::BLACKLISTED_MALWARE
, false);
463 RunLaunchTest(kDefaultAppId
, webstore_install::BLACKLISTED
, false);
464 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId
));
467 // Verifies that an app with unknown blacklist status is installed ephemerally
469 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, BlacklistStateUnknown
) {
470 // Mock a BLACKLISTED_MALWARE return status.
471 extensions::TestBlacklist
blacklist_tester(
472 ExtensionSystem::Get(profile())->blacklist());
473 blacklist_tester
.SetBlacklistState(
474 kDefaultAppId
, extensions::BLACKLISTED_UNKNOWN
, false);
476 RunLaunchTest(kDefaultAppId
, webstore_install::SUCCESS
, true);
477 ValidateAppInstalledEphemerally(kDefaultAppId
);
480 // Verifies that an app with unsupported requirements is not installed
482 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, UnsupportedRequirements
) {
483 scoped_refptr
<EphemeralAppLauncherForTest
> launcher(
484 new EphemeralAppLauncherForTest(kDefaultAppId
, profile()));
485 launcher
->set_requirements_error("App has unsupported requirements");
487 StartLauncherAndCheckResult(
488 launcher
.get(), webstore_install::REQUIREMENT_VIOLATIONS
, false);
489 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId
));
492 // Verifies that an app disabled due to permissions increase can be enabled
494 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, EnableAndLaunchApp
) {
495 const Extension
* app
= InstallAndDisableApp(
496 kDefaultAppTestPath
, Extension::DISABLE_PERMISSIONS_INCREASE
);
500 RunLaunchTest(app
->id(), webstore_install::SUCCESS
, false);
503 // Verifies that if the user cancels the enable flow, the app will not be
504 // enabled and launched.
505 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, EnableCancelled
) {
506 const Extension
* app
= InstallAndDisableApp(
507 kDefaultAppTestPath
, Extension::DISABLE_PERMISSIONS_INCREASE
);
511 RunLaunchTest(app
->id(), webstore_install::USER_CANCELLED
, false);
514 // Verifies that an installed app that had been blocked by policy cannot be
516 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, LaunchAppBlockedByPolicy
) {
517 const Extension
* app
= InstallExtension(GetTestPath(kDefaultAppTestPath
), 1);
520 // Simulate blocking of the app after it has been installed.
521 ManagementPolicyMock policy
;
522 ExtensionSystem::Get(profile())->management_policy()->RegisterProvider(
524 ExtensionSystem::Get(profile())->extension_service()->CheckManagementPolicy();
526 RunLaunchTest(app
->id(), webstore_install::BLOCKED_BY_POLICY
, false);
529 // Verifies that an installed blacklisted app cannot be launched.
530 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, LaunchBlacklistedApp
) {
531 const Extension
* app
= InstallExtension(GetTestPath(kDefaultAppTestPath
), 1);
534 ExtensionService
* service
=
535 ExtensionSystem::Get(profile())->extension_service();
536 service
->BlacklistExtensionForTest(app
->id());
538 ExtensionRegistry::Get(profile())->blacklisted_extensions().Contains(
541 RunLaunchTest(app
->id(), webstore_install::BLACKLISTED
, false);
544 // Verifies that an installed app with unsupported requirements cannot be
546 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
,
547 LaunchAppWithUnsupportedRequirements
) {
548 const Extension
* app
= InstallAndDisableApp(
549 kDefaultAppTestPath
, Extension::DISABLE_UNSUPPORTED_REQUIREMENT
);
552 RunLaunchTest(app
->id(), webstore_install::REQUIREMENT_VIOLATIONS
, false);
555 // Verifies that a launch will fail if the app is currently being installed.
556 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, InstallInProgress
) {
557 extensions::ActiveInstallData
install_data(kDefaultAppId
);
558 InstallTracker::Get(profile())->AddActiveInstall(install_data
);
560 RunLaunchTest(kDefaultAppId
, webstore_install::INSTALL_IN_PROGRESS
, false);
563 // Verifies that a launch will fail if a duplicate launch is in progress.
564 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest
, DuplicateLaunchInProgress
) {
565 extensions::ActiveInstallData
install_data(kDefaultAppId
);
566 install_data
.is_ephemeral
= true;
567 InstallTracker::Get(profile())->AddActiveInstall(install_data
);
569 RunLaunchTest(kDefaultAppId
, webstore_install::LAUNCH_IN_PROGRESS
, false);