0

Disable ephemeral apps after they stop running

Ephemeral apps are unloaded and disabled after they stop running to
ensure that they have no background activity while they are cached.

The event router, message service and message center no longer need
special handling for idle ephemeral apps.

BUG=339001,358052
TEST=browser_tests
TBR=dewittj@chromium.org (for removal of code in message_center_settings_controller.cc)

Review URL: https://codereview.chromium.org/344543006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287705 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
tmdiep@chromium.org
2014-08-06 06:26:28 +00:00
parent 3687b2f832
commit 8f959f52d7
17 changed files with 639 additions and 203 deletions

@ -31,19 +31,18 @@ class AppLifetimeMonitor : public KeyedService,
class Observer {
public:
// Called when the app starts running.
virtual void OnAppStart(Profile* profile, const std::string& app_id) = 0;
virtual void OnAppStart(Profile* profile, const std::string& app_id) {}
// Called when the app becomes active to the user, i.e. it opens a window.
virtual void OnAppActivated(Profile* profile,
const std::string& app_id) = 0;
virtual void OnAppActivated(Profile* profile, const std::string& app_id) {}
// Called when the app becomes inactive to the user.
virtual void OnAppDeactivated(Profile* profile,
const std::string& app_id) = 0;
virtual void OnAppDeactivated(Profile* profile, const std::string& app_id) {
}
// Called when the app stops running.
virtual void OnAppStop(Profile* profile, const std::string& app_id) = 0;
virtual void OnAppStop(Profile* profile, const std::string& app_id) {}
// Called when chrome is about to terminate. This gives observers a chance
// to do something before the apps shut down. This is a system-wide event
// so there is no associated profile and app id.
virtual void OnChromeTerminating() = 0;
virtual void OnChromeTerminating() {}
protected:
virtual ~Observer() {}

@ -6,11 +6,13 @@
#include <vector>
#include "apps/app_restore_service.h"
#include "apps/saved_files_service.h"
#include "base/files/scoped_temp_dir.h"
#include "base/scoped_observer.h"
#include "base/stl_util.h"
#include "chrome/browser/apps/app_browsertest_util.h"
#include "chrome/browser/apps/ephemeral_app_service.h"
#include "chrome/browser/extensions/api/file_system/file_system_api.h"
#include "chrome/browser/extensions/app_sync_data.h"
#include "chrome/browser/extensions/extension_service.h"
@ -55,9 +57,6 @@ namespace {
namespace alarms = extensions::api::alarms;
const char kNotificationsTestApp[] = "ephemeral_apps/notification_settings";
const char kFileSystemTestApp[] = "ephemeral_apps/filesystem_retain_entries";
typedef std::vector<message_center::Notifier*> NotifierList;
bool IsNotifierInList(const message_center::NotifierId& notifier_id,
@ -126,6 +125,10 @@ const char EphemeralAppTestBase::kMessagingReceiverAppV2[] =
"ephemeral_apps/messaging_receiver2";
const char EphemeralAppTestBase::kDispatchEventTestApp[] =
"ephemeral_apps/dispatch_event";
const char EphemeralAppTestBase::kNotificationsTestApp[] =
"ephemeral_apps/notification_settings";
const char EphemeralAppTestBase::kFileSystemTestApp[] =
"ephemeral_apps/filesystem_retain_entries";
EphemeralAppTestBase::EphemeralAppTestBase() {}
@ -146,6 +149,13 @@ void EphemeralAppTestBase::SetUpCommandLine(base::CommandLine* command_line) {
command_line->AppendSwitch(switches::kEnableEphemeralApps);
}
void EphemeralAppTestBase::SetUpOnMainThread() {
PlatformAppBrowserTest::SetUpOnMainThread();
// Disable ephemeral apps immediately after they stop running in tests.
EphemeralAppService::Get(profile())->set_disable_delay_for_test(0);
}
base::FilePath EphemeralAppTestBase::GetTestPath(const char* test_path) {
return test_data_dir_.AppendASCII("platform_apps").AppendASCII(test_path);
}
@ -211,7 +221,8 @@ const Extension* EphemeralAppTestBase::UpdateEphemeralApp(
&crx_installer));
windowed_observer.Wait();
return service->GetExtensionById(app_id, false);
return ExtensionRegistry::Get(profile())
->GetExtensionById(app_id, ExtensionRegistry::EVERYTHING);
}
void EphemeralAppTestBase::PromoteEphemeralApp(
@ -222,17 +233,37 @@ void EphemeralAppTestBase::PromoteEphemeralApp(
extension_service->PromoteEphemeralApp(app, false);
}
void EphemeralAppTestBase::CloseApp(const std::string& app_id) {
content::WindowedNotificationObserver event_page_destroyed_signal(
extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
content::Source<Profile>(profile()));
void EphemeralAppTestBase::DisableEphemeralApp(
const Extension* app,
Extension::DisableReason disable_reason) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
// Disabling due to a permissions increase also involves setting the
// DidExtensionEscalatePermissions flag.
if (disable_reason == Extension::DISABLE_PERMISSIONS_INCREASE)
prefs->SetDidExtensionEscalatePermissions(app, true);
ExtensionSystem::Get(profile())->extension_service()->DisableExtension(
app->id(), disable_reason);
ASSERT_TRUE(ExtensionRegistry::Get(profile())->disabled_extensions().Contains(
app->id()));
}
void EphemeralAppTestBase::CloseApp(const std::string& app_id) {
EXPECT_EQ(1U, GetAppWindowCountForApp(app_id));
apps::AppWindow* app_window = GetFirstAppWindowForApp(app_id);
ASSERT_TRUE(app_window);
CloseAppWindow(app_window);
}
event_page_destroyed_signal.Wait();
void EphemeralAppTestBase::CloseAppWaitForUnload(const std::string& app_id) {
// Ephemeral apps are unloaded from extension system after they stop running.
content::WindowedNotificationObserver unloaded_signal(
extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
content::Source<Profile>(profile()));
CloseApp(app_id);
unloaded_signal.Wait();
}
void EphemeralAppTestBase::EvictApp(const std::string& app_id) {
@ -259,6 +290,12 @@ void EphemeralAppTestBase::EvictApp(const std::string& app_id) {
class EphemeralAppBrowserTest : public EphemeralAppTestBase {
protected:
bool LaunchAppAndRunTest(const Extension* app, const char* test_name) {
// Ephemeral apps are unloaded after they are closed. Ensure they are
// enabled before launch.
ExtensionService* service =
ExtensionSystem::Get(profile())->extension_service();
service->EnableExtension(app->id());
ExtensionTestMessageListener launched_listener("launched", true);
LaunchPlatformApp(app);
if (!launched_listener.WaitUntilSatisfied()) {
@ -272,10 +309,11 @@ class EphemeralAppBrowserTest : public EphemeralAppTestBase {
bool result = catcher.GetNextResult();
message_ = catcher.message();
CloseApp(app->id());
CloseAppWaitForUnload(app->id());
return result;
}
// Verify that the event page of the app has not been loaded.
void VerifyAppNotLoaded(const std::string& app_id) {
EXPECT_FALSE(ExtensionSystem::Get(profile())->
process_manager()->GetBackgroundHostForExtension(app_id));
@ -296,6 +334,42 @@ class EphemeralAppBrowserTest : public EphemeralAppTestBase {
EXPECT_FALSE(app_sorting->GetPageOrdinal(app_id).IsValid());
}
// Verify that after ephemeral apps stop running, they reside in extension
// system in a disabled and unloaded state.
void VerifyInactiveEphemeralApp(const std::string& app_id) {
EXPECT_TRUE(
ExtensionRegistry::Get(profile())->disabled_extensions().Contains(
app_id));
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
EXPECT_TRUE(prefs->IsExtensionDisabled(app_id));
EXPECT_NE(0,
prefs->GetDisableReasons(app_id) &
Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
}
// Verify the state of an app that has been promoted from an ephemeral to a
// fully installed app.
void VerifyPromotedApp(const std::string& app_id,
ExtensionRegistry::IncludeFlag expected_set) {
const Extension* app = ExtensionRegistry::Get(profile())
->GetExtensionById(app_id, expected_set);
ASSERT_TRUE(app) << "App not found in expected set: " << expected_set;
// The app should not be ephemeral.
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
ASSERT_TRUE(prefs);
EXPECT_FALSE(prefs->IsEphemeralApp(app_id));
EXPECT_EQ(0,
prefs->GetDisableReasons(app_id) &
Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
// Check sort ordinals.
extensions::AppSorting* app_sorting = prefs->app_sorting();
EXPECT_TRUE(app_sorting->GetAppLaunchOrdinal(app_id).IsValid());
EXPECT_TRUE(app_sorting->GetPageOrdinal(app_id).IsValid());
}
// Dispatch a fake alarm event to the app.
void DispatchAlarmEvent(EventRouter* event_router,
const std::string& app_id) {
@ -310,26 +384,75 @@ class EphemeralAppBrowserTest : public EphemeralAppTestBase {
event_router->DispatchEventToExtension(app_id, event.Pass());
}
// Simulates the scenario where an app is installed, via the normal
// installation route, on top of an ephemeral app. This can occur due to race
// conditions.
const Extension* ReplaceEphemeralApp(const std::string& app_id,
const char* test_path) {
return UpdateExtensionWaitForIdle(app_id, GetTestPath(test_path), 0);
const char* test_path,
int expected_enabled_change) {
return UpdateExtensionWaitForIdle(
app_id, GetTestPath(test_path), expected_enabled_change);
}
void VerifyPromotedApp(const std::string& app_id,
ExtensionRegistry::IncludeFlag expected_set) {
const Extension* app = ExtensionRegistry::Get(profile())->GetExtensionById(
app_id, expected_set);
void PromoteEphemeralAppAndVerify(
const Extension* app,
ExtensionRegistry::IncludeFlag expected_set) {
ASSERT_TRUE(app);
// The app should not be ephemeral.
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
ASSERT_TRUE(prefs);
EXPECT_FALSE(prefs->IsEphemeralApp(app_id));
// Ephemeral apps should not be synced.
scoped_ptr<AppSyncData> sync_change = GetLastSyncChangeForApp(app->id());
EXPECT_FALSE(sync_change.get());
// Check sort ordinals.
// Promote the app to a regular installed app.
InstallObserver installed_observer(profile());
PromoteEphemeralApp(app);
VerifyPromotedApp(app->id(), expected_set);
// Check the notification parameters.
const InstallObserver::InstallParameters& params =
installed_observer.Last();
EXPECT_EQ(app->id(), params.id);
EXPECT_TRUE(params.is_update);
EXPECT_TRUE(params.from_ephemeral);
// The installation should now be synced.
sync_change = GetLastSyncChangeForApp(app->id());
VerifySyncChange(sync_change.get(),
expected_set == ExtensionRegistry::ENABLED);
}
void PromoteEphemeralAppFromSyncAndVerify(
const Extension* app,
bool enable_from_sync,
ExtensionRegistry::IncludeFlag expected_set) {
ASSERT_TRUE(app);
// Simulate an install from sync.
const syncer::StringOrdinal kAppLaunchOrdinal("x");
const syncer::StringOrdinal kPageOrdinal("y");
AppSyncData app_sync_data(*app,
enable_from_sync,
false /* incognito enabled */,
false /* remote install */,
kAppLaunchOrdinal,
kPageOrdinal,
extensions::LAUNCH_TYPE_REGULAR);
std::string app_id = app->id();
app = NULL;
ExtensionSyncService* sync_service = ExtensionSyncService::Get(profile());
sync_service->ProcessAppSyncData(app_sync_data);
// Verify the installation.
VerifyPromotedApp(app_id, expected_set);
// The sort ordinals from sync should not be overridden.
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
extensions::AppSorting* app_sorting = prefs->app_sorting();
EXPECT_TRUE(app_sorting->GetAppLaunchOrdinal(app_id).IsValid());
EXPECT_TRUE(app_sorting->GetPageOrdinal(app_id).IsValid());
EXPECT_TRUE(
app_sorting->GetAppLaunchOrdinal(app_id).Equals(kAppLaunchOrdinal));
EXPECT_TRUE(app_sorting->GetPageOrdinal(app_id).Equals(kPageOrdinal));
}
void InitSyncService() {
@ -365,6 +488,27 @@ class EphemeralAppBrowserTest : public EphemeralAppTestBase {
EXPECT_EQ(expect_enabled, sync_change->extension_sync_data().enabled());
}
void TestInstallEvent(bool close_app) {
ExtensionTestMessageListener first_msg_listener(false);
const Extension* app = InstallAndLaunchEphemeralApp(kDispatchEventTestApp);
ASSERT_TRUE(app);
// When an ephemeral app is first added, it should not receive the
// onInstalled event, hence the first message received from the test should
// be "launched" and not "installed".
ASSERT_TRUE(first_msg_listener.WaitUntilSatisfied());
EXPECT_EQ(std::string("launched"), first_msg_listener.message());
if (close_app)
CloseAppWaitForUnload(app->id());
// When installed permanently, the app should receive the onInstalled event.
ExtensionTestMessageListener install_listener("installed", false);
PromoteEphemeralApp(app);
ASSERT_TRUE(install_listener.WaitUntilSatisfied());
}
private:
syncer::FakeSyncChangeProcessor mock_sync_processor_;
};
@ -385,12 +529,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, EventDispatchWhenLaunched) {
DispatchAlarmEvent(event_router, extension->id());
ASSERT_TRUE(alarm_received_listener.WaitUntilSatisfied());
CloseApp(extension->id());
// The app needs to be launched once in order to have the onAlarm() event
// registered.
ASSERT_TRUE(event_router->ExtensionHasEventListener(
extension->id(), alarms::OnAlarm::kEventName));
CloseAppWaitForUnload(extension->id());
// Dispatch the alarm event again and verify that the event page did not get
// loaded for the app.
@ -412,13 +551,45 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
"Launched");
EXPECT_TRUE(result_catcher.GetNextResult());
CloseApp(receiver->id());
CloseAppWaitForUnload(receiver->id());
// Verify that messages are not received while the app is inactive.
LoadAndLaunchPlatformApp("ephemeral_apps/messaging_sender_fail", "Launched");
EXPECT_TRUE(result_catcher.GetNextResult());
}
// Verifies that the chrome.runtime.onInstalled() event is received by a running
// ephemeral app only when it is promoted.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
InstallEventReceivedWhileRunning) {
TestInstallEvent(false /* close app */);
}
// Verifies that when an idle ephemeral app is promoted, it will be loaded to
// receive the chrome.runtime.onInstalled() event.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, InstallEventReceivedWhileIdle) {
TestInstallEvent(true /* close app */);
}
// Verifies that the chrome.runtime.onRestarted() event is received by an
// ephemeral app.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, RestartEventReceived) {
const Extension* app = InstallAndLaunchEphemeralApp(kDispatchEventTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
// Fake ephemeral app running before restart.
ExtensionSystem::Get(profile())->extension_service()->EnableExtension(
app->id());
ASSERT_TRUE(ExtensionRegistry::Get(profile())->enabled_extensions().Contains(
app->id()));
ExtensionPrefs::Get(profile())->SetExtensionRunning(app->id(), true);
ExtensionTestMessageListener restart_listener("restarted", false);
apps::AppRestoreService::Get(profile())->HandleStartup(true);
EXPECT_TRUE(restart_listener.WaitUntilSatisfied());
}
// Verify that an updated ephemeral app will still have its ephemeral flag
// enabled.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, UpdateEphemeralApp) {
@ -426,19 +597,21 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, UpdateEphemeralApp) {
const Extension* app_v1 = InstallAndLaunchEphemeralApp(kMessagingReceiverApp);
ASSERT_TRUE(app_v1);
VerifyEphemeralApp(app_v1->id());
CloseAppWaitForUnload(app_v1->id());
VerifyInactiveEphemeralApp(app_v1->id());
std::string app_id = app_v1->id();
base::Version app_original_version = *app_v1->version();
VerifyEphemeralApp(app_id);
CloseApp(app_id);
// Update to version 2 of the app.
app_v1 = NULL; // The extension object will be destroyed during update.
InstallObserver installed_observer(profile());
const Extension* app_v2 = UpdateEphemeralApp(
app_id, GetTestPath(kMessagingReceiverAppV2),
GetTestPath(kMessagingReceiverApp).ReplaceExtension(
FILE_PATH_LITERAL(".pem")));
const Extension* app_v2 =
UpdateEphemeralApp(app_id,
GetTestPath(kMessagingReceiverAppV2),
GetTestPath(kMessagingReceiverApp)
.ReplaceExtension(FILE_PATH_LITERAL(".pem")));
// Check the notification parameters.
const InstallObserver::InstallParameters& params = installed_observer.Last();
@ -446,16 +619,19 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, UpdateEphemeralApp) {
EXPECT_TRUE(params.is_update);
EXPECT_FALSE(params.from_ephemeral);
// The ephemeral flag should still be enabled.
// The ephemeral flag should still be set.
ASSERT_TRUE(app_v2);
EXPECT_GT(app_v2->version()->CompareTo(app_original_version), 0);
VerifyEphemeralApp(app_id);
// The app should still be disabled in extension system.
VerifyInactiveEphemeralApp(app_id);
}
// Verify that if notifications have been disabled for an ephemeral app, it will
// remain disabled even after being evicted from the cache.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, StickyNotificationSettings) {
const Extension* app = InstallEphemeralApp(kNotificationsTestApp);
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
// Disable notifications for this app.
@ -470,6 +646,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, StickyNotificationSettings) {
EXPECT_FALSE(notification_service->IsNotifierEnabled(notifier_id));
// Remove the app.
CloseAppWaitForUnload(app->id());
EvictApp(app->id());
// Reinstall the ephemeral app and verify that notifications remain disabled.
@ -487,10 +664,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
IncludeRunningEphemeralAppsInNotifiers) {
message_center::NotifierSettingsProvider* settings_provider =
message_center::MessageCenter::Get()->GetNotifierSettingsProvider();
// TODO(tmdiep): Remove once notifications settings are supported across
// all platforms. This test will fail for Linux GTK.
if (!settings_provider)
return;
DCHECK(settings_provider);
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
@ -507,7 +681,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
STLDeleteElements(&notifiers);
// Close the ephemeral app.
CloseApp(app->id());
CloseAppWaitForUnload(app->id());
// Inactive ephemeral apps should not be included in the list of notifiers to
// show in the UI.
@ -550,57 +724,47 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
ASSERT_TRUE(LaunchAppAndRunTest(app, "RestoreRetainedFile")) << message_;
}
// Checks the process of installing and then promoting an ephemeral app.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteEphemeralApp) {
// Checks the process of launching an ephemeral app and then promoting the app
// while it is running.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteAppWhileRunning) {
InitSyncService();
const Extension* app = InstallEphemeralApp(kNotificationsTestApp);
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
// Ephemeral apps should not be synced.
scoped_ptr<AppSyncData> sync_change = GetLastSyncChangeForApp(app->id());
EXPECT_FALSE(sync_change.get());
PromoteEphemeralAppAndVerify(app, ExtensionRegistry::ENABLED);
// Promote the app to a regular installed app.
InstallObserver installed_observer(profile());
PromoteEphemeralApp(app);
// Ensure that the app is not unloaded and disabled after it is closed.
CloseApp(app->id());
VerifyPromotedApp(app->id(), ExtensionRegistry::ENABLED);
// Check the notification parameters.
const InstallObserver::InstallParameters& params = installed_observer.Last();
EXPECT_EQ(app->id(), params.id);
EXPECT_TRUE(params.is_update);
EXPECT_TRUE(params.from_ephemeral);
// The installation should now be synced.
sync_change = GetLastSyncChangeForApp(app->id());
VerifySyncChange(sync_change.get(), true);
}
// Verifies that promoting an ephemeral app will enable it.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteEphemeralAppAndEnable) {
// Checks the process of launching an ephemeral app and then promoting the app
// while it is idle.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteAppWhileIdle) {
InitSyncService();
const Extension* app = InstallEphemeralApp(kNotificationsTestApp);
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
VerifyInactiveEphemeralApp(app->id());
// Disable the ephemeral app due to a permissions increase. This also involves
// setting the DidExtensionEscalatePermissions flag.
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
prefs->SetDidExtensionEscalatePermissions(app, true);
ExtensionService* service =
ExtensionSystem::Get(profile())->extension_service();
service->DisableExtension(app->id(), Extension::DISABLE_PERMISSIONS_INCREASE);
ASSERT_TRUE(ExtensionRegistry::Get(profile())->
GetExtensionById(app->id(), ExtensionRegistry::DISABLED));
PromoteEphemeralAppAndVerify(app, ExtensionRegistry::ENABLED);
}
// Promote to a regular installed app. It should be enabled.
PromoteEphemeralApp(app);
VerifyPromotedApp(app->id(), ExtensionRegistry::ENABLED);
EXPECT_FALSE(prefs->DidExtensionEscalatePermissions(app->id()));
// Verifies that promoting an ephemeral app that was disabled due to a
// permissions increase will enable it.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteAppAndGrantPermissions) {
InitSyncService();
scoped_ptr<AppSyncData> sync_change = GetLastSyncChangeForApp(app->id());
VerifySyncChange(sync_change.get(), true);
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
DisableEphemeralApp(app, Extension::DISABLE_PERMISSIONS_INCREASE);
PromoteEphemeralAppAndVerify(app, ExtensionRegistry::ENABLED);
EXPECT_FALSE(ExtensionPrefs::Get(profile())
->DidExtensionEscalatePermissions(app->id()));
}
// Verifies that promoting an ephemeral app that has unsupported requirements
@ -609,57 +773,93 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
PromoteUnsupportedEphemeralApp) {
InitSyncService();
const Extension* app = InstallEphemeralApp(kNotificationsTestApp);
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
DisableEphemeralApp(app, Extension::DISABLE_UNSUPPORTED_REQUIREMENT);
// When promoted to a regular installed app, it should remain disabled.
PromoteEphemeralAppAndVerify(app, ExtensionRegistry::DISABLED);
}
// Verifies that promoting an ephemeral app that is blacklisted will not enable
// it.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
PromoteBlacklistedEphemeralApp) {
InitSyncService();
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
// Disable the ephemeral app.
ExtensionService* service =
ExtensionSystem::Get(profile())->extension_service();
service->DisableExtension(
app->id(), Extension::DISABLE_UNSUPPORTED_REQUIREMENT);
ASSERT_TRUE(ExtensionRegistry::Get(profile())->
GetExtensionById(app->id(), ExtensionRegistry::DISABLED));
service->BlacklistExtensionForTest(app->id());
ASSERT_TRUE(
ExtensionRegistry::Get(profile())->blacklisted_extensions().Contains(
app->id()));
// Promote to a regular installed app. It should remain disabled.
PromoteEphemeralApp(app);
VerifyPromotedApp(app->id(), ExtensionRegistry::DISABLED);
// When promoted to a regular installed app, it should remain blacklisted.
PromoteEphemeralAppAndVerify(app, ExtensionRegistry::BLACKLISTED);
// The app should be synced, but disabled.
scoped_ptr<AppSyncData> sync_change = GetLastSyncChangeForApp(app->id());
VerifySyncChange(sync_change.get(), false);
}
// Checks the process of promoting an ephemeral app from sync.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteEphemeralAppFromSync) {
// Checks the process of promoting an ephemeral app from sync while the app is
// running.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
PromoteAppFromSyncWhileRunning) {
InitSyncService();
const Extension* app = InstallEphemeralApp(kNotificationsTestApp);
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
std::string app_id = app->id();
// Simulate an install from sync.
const syncer::StringOrdinal kAppLaunchOrdinal("x");
const syncer::StringOrdinal kPageOrdinal("y");
AppSyncData app_sync_data(
*app,
true /* enabled */,
false /* incognito enabled */,
false /* remote install */,
kAppLaunchOrdinal,
kPageOrdinal,
extensions::LAUNCH_TYPE_REGULAR);
PromoteEphemeralAppFromSyncAndVerify(app, true, ExtensionRegistry::ENABLED);
ExtensionSyncService* sync_service = ExtensionSyncService::Get(profile());
sync_service->ProcessAppSyncData(app_sync_data);
// Ensure that the app is not unloaded and disabled after it is closed.
CloseApp(app->id());
VerifyPromotedApp(app->id(), ExtensionRegistry::ENABLED);
}
// Verify the installation.
VerifyPromotedApp(app_id, ExtensionRegistry::ENABLED);
// Checks the process of promoting an ephemeral app from sync while the app is
// idle.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteAppFromSyncWhileIdle) {
InitSyncService();
// The sort ordinals from sync should not be overridden.
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
extensions::AppSorting* app_sorting = prefs->app_sorting();
EXPECT_TRUE(app_sorting->GetAppLaunchOrdinal(app_id).Equals(
kAppLaunchOrdinal));
EXPECT_TRUE(app_sorting->GetPageOrdinal(app_id).Equals(kPageOrdinal));
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
VerifyInactiveEphemeralApp(app->id());
PromoteEphemeralAppFromSyncAndVerify(app, true, ExtensionRegistry::ENABLED);
}
// Checks the process of promoting an ephemeral app from sync, where the app
// from sync is disabled, and the ephemeral app is running.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
PromoteDisabledAppFromSyncWhileRunning) {
InitSyncService();
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
PromoteEphemeralAppFromSyncAndVerify(app, false, ExtensionRegistry::DISABLED);
}
// Checks the process of promoting an ephemeral app from sync, where the app
// from sync is disabled, and the ephemeral app is idle.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
PromoteDisabledAppFromSyncWhileIdle) {
InitSyncService();
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
VerifyInactiveEphemeralApp(app->id());
PromoteEphemeralAppFromSyncAndVerify(app, false, ExtensionRegistry::DISABLED);
}
// In most cases, ExtensionService::PromoteEphemeralApp() will be called to
@ -668,13 +868,16 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteEphemeralAppFromSync) {
// to race conditions). Ensure that the app is still installed correctly.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
ReplaceEphemeralAppWithInstalledApp) {
const Extension* app = InstallEphemeralApp(kNotificationsTestApp);
InitSyncService();
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
CloseAppWaitForUnload(app->id());
std::string app_id = app->id();
app = NULL;
InstallObserver installed_observer(profile());
ReplaceEphemeralApp(app_id, kNotificationsTestApp);
ReplaceEphemeralApp(app_id, kNotificationsTestApp, 1);
VerifyPromotedApp(app_id, ExtensionRegistry::ENABLED);
// Check the notification parameters.
@ -688,20 +891,22 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
// be delayed until the app is idle.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
ReplaceEphemeralAppWithDelayedInstalledApp) {
InitSyncService();
const Extension* app = InstallAndLaunchEphemeralApp(kNotificationsTestApp);
ASSERT_TRUE(app);
std::string app_id = app->id();
app = NULL;
// Initiate install.
ReplaceEphemeralApp(app_id, kNotificationsTestApp);
ReplaceEphemeralApp(app_id, kNotificationsTestApp, 0);
// The delayed installation will occur when the ephemeral app is closed.
content::WindowedNotificationObserver installed_signal(
extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED,
content::Source<Profile>(profile()));
InstallObserver installed_observer(profile());
CloseApp(app_id);
CloseAppWaitForUnload(app_id);
installed_signal.Wait();
VerifyPromotedApp(app_id, ExtensionRegistry::ENABLED);
@ -712,6 +917,25 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
EXPECT_TRUE(params.from_ephemeral);
}
// Verifies that an installed app cannot turn into an ephemeral app as result of
// race conditions, i.e. an ephemeral app can be promoted to an installed app,
// but not vice versa.
IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
ReplaceInstalledAppWithEphemeralApp) {
const Extension* app = InstallPlatformApp(kNotificationsTestApp);
ASSERT_TRUE(app);
std::string app_id = app->id();
app = NULL;
EXPECT_FALSE(extensions::util::IsEphemeralApp(app_id, profile()));
app =
InstallEphemeralAppWithSourceAndFlags(GetTestPath(kNotificationsTestApp),
0,
Manifest::INTERNAL,
Extension::NO_FLAGS);
EXPECT_FALSE(extensions::util::IsEphemeralApp(app_id, profile()));
}
// Ephemerality was previously encoded by the Extension::IS_EPHEMERAL creation
// flag. This was changed to an "ephemeral_app" property. Check that the prefs
// are handled correctly.

@ -21,11 +21,14 @@ class EphemeralAppTestBase : public extensions::PlatformAppBrowserTest {
static const char kMessagingReceiverApp[];
static const char kMessagingReceiverAppV2[];
static const char kDispatchEventTestApp[];
static const char kNotificationsTestApp[];
static const char kFileSystemTestApp[];
EphemeralAppTestBase();
virtual ~EphemeralAppTestBase();
virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE;
virtual void SetUpOnMainThread() OVERRIDE;
protected:
base::FilePath GetTestPath(const char* test_path);
@ -40,7 +43,10 @@ class EphemeralAppTestBase : public extensions::PlatformAppBrowserTest {
const base::FilePath& test_dir,
const base::FilePath& pem_path);
void PromoteEphemeralApp(const extensions::Extension* app);
void DisableEphemeralApp(const extensions::Extension* app,
extensions::Extension::DisableReason disable_reason);
void CloseAppWaitForUnload(const std::string& app_id);
void CloseApp(const std::string& app_id);
void EvictApp(const std::string& app_id);
};

@ -4,6 +4,7 @@
#include "base/message_loop/message_loop_proxy.h"
#include "chrome/browser/apps/ephemeral_app_launcher.h"
#include "chrome/browser/apps/ephemeral_app_service.h"
#include "chrome/browser/extensions/extension_install_checker.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
@ -20,6 +21,7 @@
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/management_policy.h"
#include "extensions/common/switches.h"
using extensions::Extension;
using extensions::ExtensionPrefs;
@ -195,10 +197,23 @@ class EphemeralAppLauncherTest : public WebstoreInstallerTest {
virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
WebstoreInstallerTest::SetUpCommandLine(command_line);
// Make event pages get suspended immediately.
command_line->AppendSwitchASCII(extensions::switches::kEventPageIdleTime,
"10");
command_line->AppendSwitchASCII(
extensions::switches::kEventPageSuspendingTime, "10");
// Enable ephemeral apps flag.
command_line->AppendSwitch(switches::kEnableEphemeralApps);
}
virtual void SetUpOnMainThread() OVERRIDE {
WebstoreInstallerTest::SetUpOnMainThread();
// Disable ephemeral apps immediately after they stop running in tests.
EphemeralAppService::Get(profile())->set_disable_delay_for_test(0);
}
base::FilePath GetTestPath(const char* test_name) {
return test_data_dir_.AppendASCII("platform_apps/ephemeral_launcher")
.AppendASCII(test_name);
@ -270,11 +285,6 @@ class EphemeralAppLauncherTest : public WebstoreInstallerTest {
if (!app)
return NULL;
if (disable_reason == Extension::DISABLE_GREYLIST) {
ExtensionPrefs::Get(profile())->SetExtensionBlacklistState(
app->id(), extensions::BLACKLISTED_MALWARE);
}
ExtensionService* service =
ExtensionSystem::Get(profile())->extension_service();
service->DisableExtension(app->id(), disable_reason);
@ -286,8 +296,8 @@ class EphemeralAppLauncherTest : public WebstoreInstallerTest {
->SetDidExtensionEscalatePermissions(app, true);
}
EXPECT_FALSE(
ExtensionRegistry::Get(profile())->enabled_extensions().Contains(
EXPECT_TRUE(
ExtensionRegistry::Get(profile())->disabled_extensions().Contains(
app->id()));
return app;
}
@ -313,6 +323,10 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTestDisabled, FeatureDisabled) {
// ephemerally and launched without prompting the user.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest,
LaunchAppWithNoPermissionWarnings) {
content::WindowedNotificationObserver unloaded_signal(
extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
content::Source<Profile>(profile()));
scoped_refptr<EphemeralAppLauncherForTest> launcher(
new EphemeralAppLauncherForTest(kDefaultAppId, profile()));
StartLauncherAndCheckResult(launcher.get(), webstore_install::SUCCESS, true);
@ -321,6 +335,9 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest,
// Apps with no permission warnings should not result in a prompt.
EXPECT_FALSE(launcher->install_prompt_created());
// Ephemeral apps are unloaded after they stop running.
unloaded_signal.Wait();
// After an app has been installed ephemerally, it can be launched again
// without installing from the web store.
RunLaunchTest(kDefaultAppId, webstore_install::SUCCESS, false);
@ -511,10 +528,16 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchAppBlockedByPolicy) {
// Verifies that an installed blacklisted app cannot be launched.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchBlacklistedApp) {
const Extension* app =
InstallAndDisableApp(kDefaultAppTestPath, Extension::DISABLE_GREYLIST);
const Extension* app = InstallExtension(GetTestPath(kDefaultAppTestPath), 1);
ASSERT_TRUE(app);
ExtensionService* service =
ExtensionSystem::Get(profile())->extension_service();
service->BlacklistExtensionForTest(app->id());
ASSERT_TRUE(
ExtensionRegistry::Get(profile())->blacklisted_extensions().Contains(
app->id()));
RunLaunchTest(app->id(), webstore_install::BLACKLISTED, false);
}

@ -4,7 +4,9 @@
#include "chrome/browser/apps/ephemeral_app_service.h"
#include "apps/app_lifetime_monitor_factory.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "chrome/browser/apps/ephemeral_app_service_factory.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
@ -44,6 +46,13 @@ const int kGarbageCollectAppsInstallDelay = 15;
// kMaxEphemeralAppsCount.
const int kGarbageCollectAppsTriggerCount = 35;
// The number of seconds after an app has stopped running before it will be
// disabled.
const int kDefaultDisableAppDelay = 1;
// The number of seconds after startup before disabling inactive ephemeral apps.
const int kDisableAppsOnStartupDelay = 5;
} // namespace
const int EphemeralAppService::kAppInactiveThreshold = 10;
@ -58,17 +67,13 @@ EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
EphemeralAppService::EphemeralAppService(Profile* profile)
: profile_(profile),
extension_registry_observer_(this),
ephemeral_app_count_(-1) {
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableEphemeralApps))
return;
extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
app_lifetime_monitor_observer_(this),
ephemeral_app_count_(-1),
disable_idle_app_delay_(kDefaultDisableAppDelay),
weak_ptr_factory_(this) {
registrar_.Add(this,
extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
content::Source<Profile>(profile_));
registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
content::Source<Profile>(profile_));
}
EphemeralAppService::~EphemeralAppService() {
@ -119,11 +124,6 @@ void EphemeralAppService::Observe(
Init();
break;
}
case chrome::NOTIFICATION_PROFILE_DESTROYED: {
// Ideally we need to know when the extension system is shutting down.
garbage_collect_apps_timer_.Stop();
break;
}
default:
NOTREACHED();
}
@ -139,8 +139,10 @@ void EphemeralAppService::OnExtensionWillBeInstalled(
// An ephemeral app was just promoted to a regular installed app.
--ephemeral_app_count_;
DCHECK_GE(ephemeral_app_count_, 0);
HandleEphemeralAppPromoted(extension);
} else if (!is_update &&
extensions::util::IsEphemeralApp(extension->id(), profile_)) {
// A new ephemeral app was launched.
++ephemeral_app_count_;
if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
TriggerGarbageCollect(
@ -159,10 +161,46 @@ void EphemeralAppService::OnExtensionUninstalled(
}
}
void EphemeralAppService::OnAppStop(Profile* profile,
const std::string& app_id) {
if (!extensions::util::IsEphemeralApp(app_id, profile_))
return;
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&EphemeralAppService::DisableEphemeralApp,
weak_ptr_factory_.GetWeakPtr(),
app_id),
base::TimeDelta::FromSeconds(disable_idle_app_delay_));
}
void EphemeralAppService::OnChromeTerminating() {
garbage_collect_apps_timer_.Stop();
extension_registry_observer_.RemoveAll();
app_lifetime_monitor_observer_.RemoveAll();
}
void EphemeralAppService::Init() {
InitEphemeralAppCount();
// Start observing.
extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
app_lifetime_monitor_observer_.Add(
apps::AppLifetimeMonitorFactory::GetForProfile(profile_));
// Execute startup clean up tasks (except during tests).
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType))
return;
TriggerGarbageCollect(
base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay));
}
void EphemeralAppService::InitEphemeralAppCount() {
@ -180,6 +218,76 @@ void EphemeralAppService::InitEphemeralAppCount() {
}
}
void EphemeralAppService::DisableEphemeralApp(const std::string& app_id) {
if (!extensions::util::IsEphemeralApp(app_id, profile_) ||
!extensions::util::IsExtensionIdle(app_id, profile_)) {
return;
}
// After an ephemeral app has stopped running, unload it from extension
// system and disable it to prevent all background activity.
ExtensionService* service =
ExtensionSystem::Get(profile_)->extension_service();
DCHECK(service);
service->DisableExtension(app_id, Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
}
void EphemeralAppService::DisableEphemeralAppsOnStartup() {
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
DCHECK(prefs);
ExtensionService* service =
ExtensionSystem::Get(profile_)->extension_service();
DCHECK(service);
// Ensure that all inactive ephemeral apps are disabled to prevent all
// background activity. This is done on startup to catch any apps that escaped
// being disabled on shutdown.
scoped_ptr<ExtensionSet> extensions =
ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
for (ExtensionSet::const_iterator it = extensions->begin();
it != extensions->end();
++it) {
const Extension* extension = *it;
if (!prefs->IsEphemeralApp(extension->id()))
continue;
// Only V2 apps are installed ephemerally. Remove other ephemeral app types
// that were cached before this policy was introduced.
if (!extension->is_platform_app()) {
service->UninstallExtension(
extension->id(),
extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
base::Bind(&base::DoNothing),
NULL);
continue;
}
if (!prefs->HasDisableReason(extension->id(),
Extension::DISABLE_INACTIVE_EPHEMERAL_APP) &&
!prefs->IsExtensionRunning(extension->id()) &&
extensions::util::IsExtensionIdle(extension->id(), profile_)) {
service->DisableExtension(extension->id(),
Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
}
}
}
void EphemeralAppService::HandleEphemeralAppPromoted(const Extension* app) {
// When ephemeral apps are promoted to regular install apps, remove the
// DISABLE_INACTIVE_EPHEMERAL_APP reason and enable the app if there are no
// other reasons.
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
DCHECK(prefs);
int disable_reasons = prefs->GetDisableReasons(app->id());
if (disable_reasons & Extension::DISABLE_INACTIVE_EPHEMERAL_APP) {
prefs->RemoveDisableReason(app->id(),
Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
if (disable_reasons == Extension::DISABLE_INACTIVE_EPHEMERAL_APP)
prefs->SetExtensionState(app->id(), Extension::ENABLED);
}
}
void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
if (!garbage_collect_apps_timer_.IsRunning()) {
garbage_collect_apps_timer_.Start(

@ -8,6 +8,8 @@
#include <map>
#include <set>
#include "apps/app_lifetime_monitor.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/timer/timer.h"
#include "components/keyed_service/core/keyed_service.h"
@ -25,7 +27,8 @@ class ExtensionRegistry;
// Performs the background garbage collection of ephemeral apps.
class EphemeralAppService : public KeyedService,
public content::NotificationObserver,
public extensions::ExtensionRegistryObserver {
public extensions::ExtensionRegistryObserver,
public apps::AppLifetimeMonitor::Observer {
public:
// Returns the instance for the given profile. This is a convenience wrapper
// around EphemeralAppServiceFactory::GetForProfile.
@ -39,6 +42,10 @@ class EphemeralAppService : public KeyedService,
int ephemeral_app_count() const { return ephemeral_app_count_; }
void set_disable_delay_for_test(int delay) {
disable_idle_app_delay_ = delay;
}
// Constants exposed for testing purposes:
// The number of days of inactivity before an ephemeral app will be removed.
@ -70,9 +77,18 @@ class EphemeralAppService : public KeyedService,
const extensions::Extension* extension,
extensions::UninstallReason reason) OVERRIDE;
// apps::AppLifetimeMonitor::Observer implementation.
virtual void OnAppStop(Profile* profile, const std::string& app_id) OVERRIDE;
virtual void OnChromeTerminating() OVERRIDE;
void Init();
void InitEphemeralAppCount();
void DisableEphemeralApp(const std::string& app_id);
void DisableEphemeralAppsOnStartup();
void HandleEphemeralAppPromoted(const extensions::Extension* app);
// Garbage collect ephemeral apps.
void TriggerGarbageCollect(const base::TimeDelta& delay);
void GarbageCollectApps();
@ -86,12 +102,20 @@ class EphemeralAppService : public KeyedService,
ScopedObserver<extensions::ExtensionRegistry,
extensions::ExtensionRegistryObserver>
extension_registry_observer_;
ScopedObserver<apps::AppLifetimeMonitor, apps::AppLifetimeMonitor::Observer>
app_lifetime_monitor_observer_;
base::OneShotTimer<EphemeralAppService> garbage_collect_apps_timer_;
// The count of cached ephemeral apps.
int ephemeral_app_count_;
// Number of seconds before disabling idle ephemeral apps.
// Overridden in tests.
int disable_idle_app_delay_;
base::WeakPtrFactory<EphemeralAppService> weak_ptr_factory_;
friend class EphemeralAppServiceTest;
friend class EphemeralAppServiceBrowserTest;

@ -50,6 +50,13 @@ class EphemeralAppServiceBrowserTest : public EphemeralAppTestBase {
ephemeral_service->InitEphemeralAppCount();
}
void DisableEphemeralAppsOnStartup() {
EphemeralAppService* ephemeral_service =
EphemeralAppService::Get(browser()->profile());
ASSERT_TRUE(ephemeral_service);
ephemeral_service->DisableEphemeralAppsOnStartup();
}
std::vector<std::string> app_ids_;
};
@ -139,7 +146,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppServiceBrowserTest, ClearCachedApps) {
InstallAndLaunchEphemeralApp(kDispatchEventTestApp);
std::string inactive_app_id = inactive_app->id();
std::string running_app_id = running_app->id();
CloseApp(inactive_app_id);
CloseAppWaitForUnload(inactive_app_id);
EphemeralAppService* ephemeral_service =
EphemeralAppService::Get(browser()->profile());
@ -157,3 +164,46 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppServiceBrowserTest, ClearCachedApps) {
EXPECT_EQ(1, ephemeral_service->ephemeral_app_count());
}
// Verify that the service will unload and disable ephemeral apps on startup.
IN_PROC_BROWSER_TEST_F(EphemeralAppServiceBrowserTest,
DisableEphemeralAppsOnStartup) {
const Extension* installed_app = InstallPlatformApp(kNotificationsTestApp);
const Extension* running_app =
InstallAndLaunchEphemeralApp(kMessagingReceiverApp);
const Extension* inactive_app = InstallEphemeralApp(kDispatchEventTestApp);
const Extension* disabled_app = InstallEphemeralApp(kFileSystemTestApp);
ASSERT_TRUE(installed_app);
ASSERT_TRUE(running_app);
ASSERT_TRUE(inactive_app);
ASSERT_TRUE(disabled_app);
DisableEphemeralApp(disabled_app, Extension::DISABLE_PERMISSIONS_INCREASE);
ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
ASSERT_TRUE(registry);
EXPECT_TRUE(registry->enabled_extensions().Contains(installed_app->id()));
EXPECT_TRUE(registry->enabled_extensions().Contains(running_app->id()));
EXPECT_TRUE(registry->enabled_extensions().Contains(inactive_app->id()));
EXPECT_TRUE(registry->disabled_extensions().Contains(disabled_app->id()));
DisableEphemeralAppsOnStartup();
// Verify that the inactive app is disabled.
EXPECT_TRUE(registry->enabled_extensions().Contains(installed_app->id()));
EXPECT_TRUE(registry->enabled_extensions().Contains(running_app->id()));
EXPECT_TRUE(registry->disabled_extensions().Contains(inactive_app->id()));
EXPECT_TRUE(registry->disabled_extensions().Contains(disabled_app->id()));
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
ASSERT_TRUE(prefs);
EXPECT_FALSE(prefs->HasDisableReason(
installed_app->id(), Extension::DISABLE_INACTIVE_EPHEMERAL_APP));
EXPECT_FALSE(prefs->HasDisableReason(
running_app->id(), Extension::DISABLE_INACTIVE_EPHEMERAL_APP));
EXPECT_TRUE(prefs->HasDisableReason(
inactive_app->id(), Extension::DISABLE_INACTIVE_EPHEMERAL_APP));
EXPECT_TRUE(prefs->HasDisableReason(
disabled_app->id(), Extension::DISABLE_INACTIVE_EPHEMERAL_APP));
EXPECT_TRUE(prefs->HasDisableReason(
disabled_app->id(), Extension::DISABLE_PERMISSIONS_INCREASE));
}

@ -4,6 +4,7 @@
#include "chrome/browser/apps/ephemeral_app_service_factory.h"
#include "apps/app_lifetime_monitor_factory.h"
#include "chrome/browser/apps/ephemeral_app_service.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
@ -29,6 +30,7 @@ EphemeralAppServiceFactory::EphemeralAppServiceFactory()
"EphemeralAppService",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
DependsOn(apps::AppLifetimeMonitorFactory::GetInstance());
}
EphemeralAppServiceFactory::~EphemeralAppServiceFactory() {

@ -31,7 +31,6 @@
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/lazy_background_task_queue.h"
#include "extensions/browser/process_manager.h"
@ -216,15 +215,6 @@ void MessageService::OpenChannelToExtension(
return;
}
// Only running ephemeral apps can receive messages. Idle cached ephemeral
// apps are invisible and should not be connectable.
if (util::IsEphemeralApp(target_extension_id, context) &&
util::IsExtensionIdle(target_extension_id, context)) {
DispatchOnDisconnect(
source, receiver_port_id, kReceivingEndDoesntExistError);
return;
}
bool is_web_connection = false;
if (source_extension_id != target_extension_id) {

@ -163,6 +163,14 @@ void ExtensionService::AddProviderForTesting(
linked_ptr<extensions::ExternalProviderInterface>(test_provider));
}
void ExtensionService::BlacklistExtensionForTest(
const std::string& extension_id) {
ExtensionIdSet blocked;
ExtensionIdSet unchanged;
blocked.insert(extension_id);
UpdateBlockedExtensions(blocked, unchanged);
}
bool ExtensionService::OnExternalExtensionUpdateUrlFound(
const std::string& id,
const std::string& install_parameter,
@ -845,9 +853,11 @@ void ExtensionService::DisableExtension(
Extension::DisableReason disable_reason) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// The extension may have been disabled already.
if (!IsExtensionEnabled(extension_id))
// The extension may have been disabled already. Just add a disable reason.
if (!IsExtensionEnabled(extension_id)) {
extension_prefs_->AddDisableReason(extension_id, disable_reason);
return;
}
const Extension* extension = GetInstalledExtension(extension_id);
// |extension| can be NULL if sync disables an extension that is not
@ -1658,6 +1668,12 @@ void ExtensionService::OnExtensionInstalled(
extension->GetType(), 100);
UMA_HISTOGRAM_ENUMERATION("Extensions.UpdateSource",
extension->location(), Manifest::NUM_LOCATIONS);
// A fully installed app cannot be demoted to an ephemeral app.
if ((install_flags & extensions::kInstallFlagIsEphemeral) &&
!extension_prefs_->IsEphemeralApp(id)) {
install_flags &= ~static_cast<int>(extensions::kInstallFlagIsEphemeral);
}
}
const Extension::State initial_state =
@ -1837,13 +1853,6 @@ void ExtensionService::PromoteEphemeralApp(
extension->id(), syncer::StringOrdinal());
}
if (!is_from_sync) {
// Cached ephemeral apps may be updated and disabled due to permissions
// increase. The app can be enabled as the install was user-acknowledged.
if (extension_prefs_->DidExtensionEscalatePermissions(extension->id()))
GrantPermissionsAndEnableExtension(extension);
}
// Remove the ephemeral flags from the preferences.
extension_prefs_->OnEphemeralAppPromoted(extension->id());
@ -1866,12 +1875,30 @@ void ExtensionService::PromoteEphemeralApp(
extension->name() /* old name */);
if (registry_->enabled_extensions().Contains(extension->id())) {
// If the app is already enabled and loaded, fire the load events to allow
// observers to handle the promotion of the ephemeral app.
content::NotificationService::current()->Notify(
extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
content::Source<Profile>(profile_),
content::Details<const Extension>(extension));
registry_->TriggerOnLoaded(extension);
} else {
// Cached ephemeral apps may be updated and disabled due to permissions
// increase. The app can be enabled (as long as no other disable reasons
// exist) as the install was user-acknowledged.
int disable_mask = Extension::DISABLE_NONE;
if (!is_from_sync)
disable_mask |= Extension::DISABLE_PERMISSIONS_INCREASE;
int other_disable_reasons =
extension_prefs_->GetDisableReasons(extension->id()) & ~disable_mask;
if (!other_disable_reasons) {
if (extension_prefs_->DidExtensionEscalatePermissions(extension->id()))
GrantPermissionsAndEnableExtension(extension);
else
EnableExtension(extension->id());
}
}
registry_->TriggerOnInstalled(extension, true);

@ -427,6 +427,9 @@ class ExtensionService
void AddProviderForTesting(
extensions::ExternalProviderInterface* test_provider);
// Simulate an extension being blacklisted for tests.
void BlacklistExtensionForTest(const std::string& extension_id);
#if defined(UNIT_TEST)
void TrackTerminatedExtensionForTest(const extensions::Extension* extension) {
TrackTerminatedExtension(extension);

@ -15,7 +15,6 @@
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
#include "chrome/browser/extensions/app_icon_loader_impl.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/favicon/favicon_service.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/history/history_types.h"
@ -34,7 +33,6 @@
#include "content/public/browser/notification_source.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/permissions_data.h"
@ -234,12 +232,6 @@ void MessageCenterSettingsController::GetNotifierList(
continue;
}
// Exclude cached ephemeral apps that are not currently running.
if (extensions::util::IsEphemeralApp(extension->id(), profile) &&
extensions::util::IsExtensionIdle(extension->id(), profile)) {
continue;
}
NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
notifiers->push_back(new Notifier(
notifier_id,

@ -6,6 +6,14 @@ chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', {});
});
chrome.app.runtime.onRestarted.addListener(function() {
chrome.test.sendMessage('restarted');
});
chrome.alarms.onAlarm.addListener(function(alarmInfo) {
chrome.test.sendMessage('alarm_received');
});
chrome.runtime.onInstalled.addListener(function() {
chrome.test.sendMessage('installed');
});

@ -32,7 +32,6 @@ void DispatchOnEmbedRequestedEventImpl(
scoped_ptr<Event> event(
new Event(app_runtime::OnEmbedRequested::kEventName, args.Pass()));
event->restrict_to_browser_context = context;
event->can_load_ephemeral_apps = true;
system->event_router()->DispatchEventWithLazyListener(extension_id,
event.Pass());
@ -52,7 +51,6 @@ void DispatchOnLaunchedEventImpl(const std::string& extension_id,
scoped_ptr<Event> event(
new Event(app_runtime::OnLaunched::kEventName, args.Pass()));
event->restrict_to_browser_context = context;
event->can_load_ephemeral_apps = true;
EventRouter::Get(context)
->DispatchEventWithLazyListener(extension_id, event.Pass());
ExtensionPrefs::Get(context)
@ -86,7 +84,6 @@ void AppRuntimeEventRouter::DispatchOnRestartedEvent(
scoped_ptr<Event> event(
new Event(app_runtime::OnRestarted::kEventName, arguments.Pass()));
event->restrict_to_browser_context = context;
event->can_load_ephemeral_apps = true;
EventRouter::Get(context)
->DispatchEventToExtension(extension->id(), event.Pass());
}

@ -19,7 +19,6 @@
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/lazy_background_task_queue.h"
#include "extensions/browser/notification_types.h"
@ -625,15 +624,6 @@ bool EventRouter::MaybeLoadLazyBackgroundPageToDispatchEvent(
BrowserContext* context,
const Extension* extension,
const linked_ptr<Event>& event) {
if (util::IsEphemeralApp(extension->id(), context) &&
!event->can_load_ephemeral_apps) {
// Most events can only be dispatched to ephemeral apps that are already
// running.
ProcessManager* pm = ExtensionSystem::Get(context)->process_manager();
if (!pm->GetBackgroundHostForExtension(extension->id()))
return false;
}
if (!CanDispatchEventToBrowserContext(context, extension, event))
return false;
@ -774,8 +764,7 @@ Event::Event(const std::string& event_name,
: event_name(event_name),
event_args(event_args.Pass()),
restrict_to_browser_context(NULL),
user_gesture(EventRouter::USER_GESTURE_UNKNOWN),
can_load_ephemeral_apps(false) {
user_gesture(EventRouter::USER_GESTURE_UNKNOWN) {
DCHECK(this->event_args.get());
}
@ -785,8 +774,7 @@ Event::Event(const std::string& event_name,
: event_name(event_name),
event_args(event_args.Pass()),
restrict_to_browser_context(restrict_to_browser_context),
user_gesture(EventRouter::USER_GESTURE_UNKNOWN),
can_load_ephemeral_apps(false) {
user_gesture(EventRouter::USER_GESTURE_UNKNOWN) {
DCHECK(this->event_args.get());
}
@ -801,8 +789,7 @@ Event::Event(const std::string& event_name,
restrict_to_browser_context(restrict_to_browser_context),
event_url(event_url),
user_gesture(user_gesture),
filter_info(filter_info),
can_load_ephemeral_apps(false) {
filter_info(filter_info) {
DCHECK(this->event_args.get());
}

@ -351,12 +351,6 @@ struct Event {
// this event to be dispatched to non-extension processes, like WebUI.
WillDispatchCallback will_dispatch_callback;
// If true, this event will always be dispatched to ephemeral apps, regardless
// of whether they are running or inactive. Defaults to false.
// Most events can only be dispatched to ephemeral apps that are already
// running. Cached ephemeral apps are inactive until launched by the user.
bool can_load_ephemeral_apps;
Event(const std::string& event_name,
scoped_ptr<base::ListValue> event_args);

@ -96,7 +96,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
DISABLE_GREYLIST = 1 << 9,
DISABLE_CORRUPTED = 1 << 10,
DISABLE_REMOTE_INSTALL = 1 << 11,
DISABLE_REASON_LAST = 1 << 12, // This should always be the last value
DISABLE_INACTIVE_EPHEMERAL_APP = 1 << 12, // Cached ephemeral apps are
// disabled to prevent activity.
DISABLE_REASON_LAST = 1 << 13, // This should always be the last value
};
// A base class for parsed manifest data that APIs want to store on