summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortmdiep@chromium.org <tmdiep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-21 12:58:34 +0000
committertmdiep@chromium.org <tmdiep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-21 12:58:34 +0000
commitca033634a786022407b66ca4c65fc910eae58d39 (patch)
treead9728883852c1b0df65627237aaa1fd99cbea6e
parenta12f8b5e6ecd9ff97969bb7cb2037865d1d4f09e (diff)
downloadchromium_src-ca033634a786022407b66ca4c65fc910eae58d39.zip
chromium_src-ca033634a786022407b66ca4c65fc910eae58d39.tar.gz
chromium_src-ca033634a786022407b66ca4c65fc910eae58d39.tar.bz2
Retain local data of ephemeral apps after cache eviction
This patch prevents the local data of ephemeral apps from being deleted when they are evicted from the cache (i.e. uninstalled from extension system). The data is garbage collected by EphemeralAppService after a period of inactivity. BUG=339004 TEST=browser_tests (EphemeralAppBrowserTest.*) Review URL: https://codereview.chromium.org/202763005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@258555 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/apps/ephemeral_app_browsertest.cc226
-rw-r--r--chrome/browser/apps/ephemeral_app_service.cc95
-rw-r--r--chrome/browser/apps/ephemeral_app_service.h18
-rw-r--r--chrome/browser/apps/ephemeral_app_service_browsertest.cc2
-rw-r--r--chrome/browser/chrome_content_browser_client.cc15
-rw-r--r--chrome/browser/extensions/extension_service.cc19
-rw-r--r--chrome/browser/extensions/extension_util.cc41
-rw-r--r--chrome/browser/extensions/extension_util.h9
-rw-r--r--chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data.pem15
-rw-r--r--chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.html11
-rw-r--r--chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.js180
-rw-r--r--chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/main.js7
-rw-r--r--chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/manifest.json12
-rw-r--r--extensions/browser/extension_prefs.cc75
-rw-r--r--extensions/browser/extension_prefs.h11
15 files changed, 665 insertions, 71 deletions
diff --git a/chrome/browser/apps/ephemeral_app_browsertest.cc b/chrome/browser/apps/ephemeral_app_browsertest.cc
index 6f68ba7..67c52e5 100644
--- a/chrome/browser/apps/ephemeral_app_browsertest.cc
+++ b/chrome/browser/apps/ephemeral_app_browsertest.cc
@@ -6,15 +6,18 @@
#include "base/files/scoped_temp_dir.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/extension_service.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/notifications/desktop_notification_service.h"
#include "chrome/browser/notifications/desktop_notification_service_factory.h"
#include "chrome/common/extensions/api/alarms.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/switches.h"
@@ -24,27 +27,22 @@
using extensions::Event;
using extensions::EventRouter;
using extensions::Extension;
+using extensions::ExtensionInfo;
+using extensions::ExtensionPrefs;
using extensions::ExtensionSystem;
+using extensions::Manifest;
using extensions::PlatformAppBrowserTest;
namespace {
namespace alarms = extensions::api::alarms;
-const char kDispatchEventTestApp[] =
- "platform_apps/ephemeral_apps/dispatch_event";
-
-const char kMessagingReceiverApp[] =
- "platform_apps/ephemeral_apps/messaging_receiver";
-
-const char kMessagingReceiverAppV2[] =
- "platform_apps/ephemeral_apps/messaging_receiver2";
-
-const char kNotificationsTestApp[] =
- "platform_apps/ephemeral_apps/notification_settings";
-
-const char kFileSystemTestApp[] =
- "platform_apps/ephemeral_apps/filesystem_retain_entries";
+const char kDispatchEventTestApp[] = "ephemeral_apps/dispatch_event";
+const char kMessagingReceiverApp[] = "ephemeral_apps/messaging_receiver";
+const char kMessagingReceiverAppV2[] = "ephemeral_apps/messaging_receiver2";
+const char kNotificationsTestApp[] = "ephemeral_apps/notification_settings";
+const char kFileSystemTestApp[] = "ephemeral_apps/filesystem_retain_entries";
+const char kRetainDataApp[] = "ephemeral_apps/retain_data";
typedef std::vector<message_center::Notifier*> NotifierList;
@@ -60,6 +58,19 @@ bool IsNotifierInList(const message_center::NotifierId& notifier_id,
return false;
}
+bool IsAppInExtensionsInfo(const ExtensionPrefs::ExtensionsInfo& ext_info,
+ const std::string& extension_id) {
+ for (size_t i = 0; i < ext_info.size(); ++i) {
+ ExtensionInfo* info = ext_info.at(i).get();
+ if (info->extension_id == extension_id)
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
class EphemeralAppBrowserTest : public PlatformAppBrowserTest {
protected:
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
@@ -74,17 +85,25 @@ class EphemeralAppBrowserTest : public PlatformAppBrowserTest {
extensions::switches::kEventPageSuspendingTime, "10");
}
- const Extension* InstallEphemeralApp(const char* test_path) {
- base::FilePath path = test_data_dir_.AppendASCII(test_path);
+ base::FilePath GetTestPath(const char* test_path) {
+ return test_data_dir_.AppendASCII("platform_apps").AppendASCII(test_path);
+ }
+
+ const Extension* InstallEphemeralApp(const char* test_path,
+ Manifest::Location manifest_location) {
const Extension* extension =
InstallExtensionWithSourceAndFlags(
- path,
+ GetTestPath(test_path),
1,
- extensions::Manifest::UNPACKED,
+ manifest_location,
Extension::IS_EPHEMERAL);
return extension;
}
+ const Extension* InstallEphemeralApp(const char* test_path) {
+ return InstallEphemeralApp(test_path, Manifest::INTERNAL);
+ }
+
const Extension* InstallAndLaunchEphemeralApp(const char* test_path) {
ExtensionTestMessageListener launched_listener("launched", false);
const Extension* extension = InstallEphemeralApp(test_path);
@@ -134,6 +153,21 @@ class EphemeralAppBrowserTest : public PlatformAppBrowserTest {
event_page_destroyed_signal.Wait();
}
+ void EvictApp(const std::string& app_id) {
+ // Uninstall the app, which is what happens when ephemeral apps get evicted
+ // from the cache.
+ content::WindowedNotificationObserver uninstalled_signal(
+ chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
+ content::Source<Profile>(browser()->profile()));
+
+ ExtensionService* service =
+ ExtensionSystem::Get(browser()->profile())->extension_service();
+ ASSERT_TRUE(service);
+ service->UninstallExtension(app_id, false, NULL);
+
+ uninstalled_signal.Wait();
+ }
+
void VerifyAppNotLoaded(const std::string& app_id) {
EXPECT_FALSE(ExtensionSystem::Get(browser()->profile())->
process_manager()->GetBackgroundHostForExtension(app_id));
@@ -151,9 +185,14 @@ class EphemeralAppBrowserTest : public PlatformAppBrowserTest {
event_router->DispatchEventToExtension(app_id, event.Pass());
}
-};
-} // namespace
+ void GarbageCollectData() {
+ EphemeralAppService* service =
+ EphemeralAppService::Get(browser()->profile());
+ ASSERT_TRUE(service);
+ service->GarbageCollectData();
+ }
+};
// Verify that ephemeral apps can be launched and receive system events when
// they are running. Once they are inactive they should not receive system
@@ -225,9 +264,9 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, UpdateEphemeralApp) {
}
base::FilePath app_v2_path = PackExtensionWithOptions(
- test_data_dir_.AppendASCII(kMessagingReceiverAppV2),
+ GetTestPath(kMessagingReceiverAppV2),
crx_path,
- test_data_dir_.AppendASCII(kMessagingReceiverApp).ReplaceExtension(
+ GetTestPath(kMessagingReceiverApp).ReplaceExtension(
FILE_PATH_LITERAL(".pem")),
base::FilePath());
ASSERT_FALSE(app_v2_path.empty());
@@ -266,18 +305,8 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, StickyNotificationSettings) {
notification_service->SetNotifierEnabled(notifier_id, false);
EXPECT_FALSE(notification_service->IsNotifierEnabled(notifier_id));
- // Uninstall the app, which is what happens when ephemeral apps get evicted
- // from the cache.
- content::WindowedNotificationObserver uninstalled_signal(
- chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
- content::Source<Profile>(browser()->profile()));
-
- ExtensionService* service =
- ExtensionSystem::Get(browser()->profile())->extension_service();
- ASSERT_TRUE(service);
- service->UninstallExtension(app->id(), false, NULL);
-
- uninstalled_signal.Wait();
+ // Remove the app.
+ EvictApp(app->id());
// Reinstall the ephemeral app and verify that notifications remain disabled.
app = InstallEphemeralApp(kNotificationsTestApp);
@@ -342,7 +371,8 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
"temp", temp_dir.path());
// The first test opens the file and writes the file handle to local storage.
- const Extension* app = InstallEphemeralApp(kFileSystemTestApp);
+ const Extension* app = InstallEphemeralApp(kFileSystemTestApp,
+ Manifest::UNPACKED);
ASSERT_TRUE(LaunchAppAndRunTest(app, "OpenAndRetainFile")) << message_;
// Verify that after the app has been closed, all retained entries are
@@ -350,7 +380,133 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest,
std::vector<apps::SavedFileEntry> file_entries =
apps::SavedFilesService::Get(browser()->profile())
->GetAllFileEntries(app->id());
+ EXPECT_TRUE(file_entries.empty());
// The second test verifies that the file cannot be reopened.
ASSERT_TRUE(LaunchAppAndRunTest(app, "RestoreRetainedFile")) << message_;
}
+
+// Verify that once evicted from the cache, the data of ephemeral apps will not
+// be deleted.
+IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, RetainData) {
+ // Phase 1 - Install the ephemeral app and write data to various storage.
+ const Extension* app = InstallEphemeralApp(kRetainDataApp);
+ ASSERT_TRUE(app);
+ ASSERT_TRUE(LaunchAppAndRunTest(app, "WriteData")) << message_;
+
+ // Sanity check to ensure that the ReadData tests should pass before the app
+ // is removed.
+ ASSERT_TRUE(LaunchAppAndRunTest(app, "ReadData")) << message_;
+
+ // Remove the app.
+ const std::string app_id = app->id();
+ EvictApp(app->id());
+ app = NULL;
+
+ // The app should be in the list of evicted apps.
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser()->profile());
+ ASSERT_TRUE(prefs);
+ scoped_ptr<ExtensionPrefs::ExtensionsInfo> extensions_info(
+ prefs->GetEvictedEphemeralAppsInfo());
+ EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info, app_id));
+
+ // The app should not be in the list of installed extensions.
+ extensions_info = prefs->GetInstalledExtensionsInfo();
+ EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info, app_id));
+
+ // Ensure the evicted app is considered to have isolated storage. This will
+ // prevent its data from getting garbage collected by
+ // ExtensionService::GarbageCollectIsolatedStorage().
+ GURL site_url = extensions::util::GetSiteForExtensionId(
+ app_id, browser()->profile());
+ EXPECT_TRUE(extensions::util::SiteHasIsolatedStorage(
+ site_url, browser()->profile()));
+
+ // Phase 2 - Reinstall the ephemeral app and verify that data still exists
+ // in the storage.
+ app = InstallEphemeralApp(kRetainDataApp);
+ ASSERT_TRUE(app);
+ EXPECT_TRUE(LaunchAppAndRunTest(app, "ReadData")) << message_;
+
+ // The app should now be in the list of installed extensions, but not in the
+ // list of evicted apps.
+ extensions_info = prefs->GetInstalledExtensionsInfo();
+ EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info, app_id));
+ extensions_info = prefs->GetEvictedEphemeralAppsInfo();
+ EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info, app_id));
+}
+
+// Verify that the data of regular installed apps are deleted on uninstall.
+IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, RemoveInstalledData) {
+ // Install the ephemeral app and write data to various storage.
+ const Extension* app = InstallPlatformApp(kRetainDataApp);
+ ASSERT_TRUE(app);
+ ASSERT_TRUE(LaunchAppAndRunTest(app, "WriteData")) << message_;
+
+ // Remove the app.
+ const std::string app_id = app->id();
+ EvictApp(app->id());
+ app = NULL;
+
+ // The app should not be in the preferences.
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser()->profile());
+ ASSERT_TRUE(prefs);
+ scoped_ptr<ExtensionPrefs::ExtensionsInfo> extensions_info(
+ prefs->GetEvictedEphemeralAppsInfo());
+ EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info, app_id));
+ extensions_info = prefs->GetInstalledExtensionsInfo();
+ EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info, app_id));
+
+ // Reinstall the app and verify that all data has been reset.
+ app = InstallPlatformApp(kRetainDataApp);
+ ASSERT_TRUE(LaunchAppAndRunTest(app, "DataReset")) << message_;
+}
+
+// Verify that once evicted from the cache, ephemeral apps will remain in
+// extension prefs, but marked as evicted. After garbage collection of data,
+// both their data and preferences should be removed.
+IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, GarbageCollectData) {
+ // Create two apps. Both will be evicted from the cache, but the data of
+ // one will be garbage collected.
+ const Extension* evict_app = InstallEphemeralApp(kRetainDataApp);
+ ASSERT_TRUE(evict_app);
+ ASSERT_TRUE(LaunchAppAndRunTest(evict_app, "WriteData")) << message_;
+ std::string evict_app_id = evict_app->id();
+ EvictApp(evict_app_id);
+ evict_app = NULL;
+
+ const Extension* retain_app = InstallEphemeralApp(kDispatchEventTestApp);
+ ASSERT_TRUE(retain_app);
+ std::string retain_app_id = retain_app->id();
+ EvictApp(retain_app_id);
+ retain_app = NULL;
+
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(browser()->profile());
+ ASSERT_TRUE(prefs);
+
+ // Both apps should be in the list of evicted apps.
+ scoped_ptr<ExtensionPrefs::ExtensionsInfo> extensions_info(
+ prefs->GetEvictedEphemeralAppsInfo());
+ EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info, retain_app_id));
+ EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info, evict_app_id));
+
+ // Set a fake last launch time so that the ephemeral app's data will be
+ // garbage collected.
+ base::Time launch_time =
+ base::Time::Now() - base::TimeDelta::FromDays(
+ EphemeralAppService::kDataInactiveThreshold + 1);
+ prefs->SetLastLaunchTime(evict_app_id, launch_time);
+ prefs->SetLastLaunchTime(retain_app_id, base::Time::Now());
+
+ // Garbage collect data.
+ GarbageCollectData();
+
+ // The garbage collected app should no longer be in the preferences.
+ extensions_info = prefs->GetEvictedEphemeralAppsInfo();
+ EXPECT_TRUE(IsAppInExtensionsInfo(*extensions_info, retain_app_id));
+ ASSERT_FALSE(IsAppInExtensionsInfo(*extensions_info, evict_app_id));
+
+ // Reinstall the app and verify that all data has been reset.
+ evict_app = InstallEphemeralApp(kRetainDataApp);
+ ASSERT_TRUE(LaunchAppAndRunTest(evict_app, "DataReset")) << message_;
+}
diff --git a/chrome/browser/apps/ephemeral_app_service.cc b/chrome/browser/apps/ephemeral_app_service.cc
index 86ea100..0832e14 100644
--- a/chrome/browser/apps/ephemeral_app_service.cc
+++ b/chrome/browser/apps/ephemeral_app_service.cc
@@ -7,6 +7,7 @@
#include "base/command_line.h"
#include "chrome/browser/apps/ephemeral_app_service_factory.h"
#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/data_deleter.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
@@ -21,6 +22,7 @@
#include "extensions/common/extension_set.h"
using extensions::Extension;
+using extensions::ExtensionInfo;
using extensions::ExtensionPrefs;
using extensions::ExtensionSet;
using extensions::ExtensionSystem;
@@ -30,28 +32,27 @@ namespace {
// The number of seconds after startup before performing garbage collection
// of ephemeral apps.
-const int kGarbageCollectStartupDelay = 60;
+const int kGarbageCollectAppsStartupDelay = 60;
// The number of seconds after an ephemeral app has been installed before
// performing garbage collection.
-const int kGarbageCollectInstallDelay = 15;
+const int kGarbageCollectAppsInstallDelay = 15;
// When the number of ephemeral apps reaches this count, trigger garbage
// collection to trim off the least-recently used apps in excess of
// kMaxEphemeralAppsCount.
-const int kGarbageCollectTriggerCount = 35;
+const int kGarbageCollectAppsTriggerCount = 35;
+
+// The number of seconds after startup before performing garbage collection
+// of the data of evicted ephemeral apps.
+const int kGarbageCollectDataStartupDelay = 120;
} // namespace
-// The number of days of inactivity before an ephemeral app will be removed.
const int EphemeralAppService::kAppInactiveThreshold = 10;
-
-// If the ephemeral app has been launched within this number of days, it will
-// definitely not be garbage collected.
const int EphemeralAppService::kAppKeepThreshold = 1;
-
-// The maximum number of ephemeral apps to keep cached. Excess may be removed.
const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
+const int EphemeralAppService::kDataInactiveThreshold = 90;
// static
EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
@@ -93,9 +94,9 @@ void EphemeralAppService::Observe(
DCHECK(extension);
if (extension->is_ephemeral()) {
++ephemeral_app_count_;
- if (ephemeral_app_count_ >= kGarbageCollectTriggerCount)
+ if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount)
TriggerGarbageCollect(
- base::TimeDelta::FromSeconds(kGarbageCollectInstallDelay));
+ base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
}
break;
}
@@ -109,7 +110,7 @@ void EphemeralAppService::Observe(
}
case chrome::NOTIFICATION_PROFILE_DESTROYED: {
// Ideally we need to know when the extension system is shutting down.
- garbage_collect_timer_.Stop();
+ garbage_collect_apps_timer_.Stop();
break;
}
default:
@@ -120,7 +121,13 @@ void EphemeralAppService::Observe(
void EphemeralAppService::Init() {
InitEphemeralAppCount();
TriggerGarbageCollect(
- base::TimeDelta::FromSeconds(kGarbageCollectStartupDelay));
+ base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
+
+ garbage_collect_data_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromSeconds(kGarbageCollectDataStartupDelay),
+ this,
+ &EphemeralAppService::GarbageCollectData);
}
void EphemeralAppService::InitEphemeralAppCount() {
@@ -138,12 +145,13 @@ void EphemeralAppService::InitEphemeralAppCount() {
}
void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
- if (!garbage_collect_timer_.IsRunning())
- garbage_collect_timer_.Start(
- FROM_HERE,
- delay,
- this,
- &EphemeralAppService::GarbageCollectApps);
+ if (!garbage_collect_apps_timer_.IsRunning()) {
+ garbage_collect_apps_timer_.Start(
+ FROM_HERE,
+ delay,
+ this,
+ &EphemeralAppService::GarbageCollectApps);
+ }
}
void EphemeralAppService::GarbageCollectApps() {
@@ -225,3 +233,52 @@ void EphemeralAppService::GetAppsToRemove(
}
}
}
+
+void EphemeralAppService::GarbageCollectData() {
+ ExtensionService* service =
+ ExtensionSystem::Get(profile_)->extension_service();
+ DCHECK(service);
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+ DCHECK(prefs);
+ scoped_ptr<ExtensionPrefs::ExtensionsInfo> evicted_apps_info(
+ prefs->GetEvictedEphemeralAppsInfo());
+
+ base::Time time_now = base::Time::Now();
+ const base::Time inactive_threshold =
+ time_now - base::TimeDelta::FromDays(kDataInactiveThreshold);
+
+ for (size_t i = 0; i < evicted_apps_info->size(); ++i) {
+ ExtensionInfo* info = evicted_apps_info->at(i).get();
+ base::Time last_launch_time = prefs->GetLastLaunchTime(info->extension_id);
+ if (last_launch_time > inactive_threshold)
+ continue;
+
+ // Sanity check to ensure the app is not currently installed.
+ if (service->GetInstalledExtension(info->extension_id)) {
+ NOTREACHED();
+ continue;
+ }
+
+ // Ensure the app is not waiting to be installed.
+ scoped_ptr<ExtensionInfo> delayed_install(
+ prefs->GetDelayedInstallInfo(info->extension_id));
+ if (delayed_install.get())
+ continue;
+
+ if (info->extension_manifest.get()) {
+ std::string error;
+ scoped_refptr<const Extension> extension(Extension::Create(
+ info->extension_path,
+ info->extension_location,
+ *info->extension_manifest,
+ prefs->GetCreationFlags(info->extension_id),
+ info->extension_id,
+ &error));
+
+ if (extension.get())
+ extensions::DataDeleter::StartDeleting(profile_, extension.get());
+ }
+
+ prefs->RemoveEvictedEphemeralApp(info->extension_id);
+ }
+}
diff --git a/chrome/browser/apps/ephemeral_app_service.h b/chrome/browser/apps/ephemeral_app_service.h
index ac8dea3..a1d4fbb 100644
--- a/chrome/browser/apps/ephemeral_app_service.h
+++ b/chrome/browser/apps/ephemeral_app_service.h
@@ -30,10 +30,18 @@ class EphemeralAppService : public KeyedService,
explicit EphemeralAppService(Profile* profile);
virtual ~EphemeralAppService();
- // Constants exposed for testing purposes.
+ // Constants exposed for testing purposes:
+
+ // The number of days of inactivity before an ephemeral app will be removed.
static const int kAppInactiveThreshold;
+ // If the ephemeral app has been launched within this number of days, it will
+ // definitely not be garbage collected.
static const int kAppKeepThreshold;
+ // The maximum number of ephemeral apps to keep cached. Excess may be removed.
static const int kMaxEphemeralAppsCount;
+ // The number of days of inactivity before the data of an already evicted
+ // ephemeral app will be removed.
+ static const int kDataInactiveThreshold;
private:
// A map used to order the ephemeral apps by their last launch time.
@@ -54,15 +62,21 @@ class EphemeralAppService : public KeyedService,
const LaunchTimeAppMap& app_launch_times,
std::set<std::string>* remove_app_ids);
+ // Garbage collect the data of ephemeral apps that have been evicted and
+ // inactive for a long period of time.
+ void GarbageCollectData();
+
Profile* profile_;
content::NotificationRegistrar registrar_;
- base::OneShotTimer<EphemeralAppService> garbage_collect_timer_;
+ base::OneShotTimer<EphemeralAppService> garbage_collect_apps_timer_;
+ base::OneShotTimer<EphemeralAppService> garbage_collect_data_timer_;
// The count of cached ephemeral apps.
int ephemeral_app_count_;
+ friend class EphemeralAppBrowserTest;
friend class EphemeralAppServiceTest;
friend class EphemeralAppServiceBrowserTest;
diff --git a/chrome/browser/apps/ephemeral_app_service_browsertest.cc b/chrome/browser/apps/ephemeral_app_service_browsertest.cc
index ccf0bbf..e59eec2 100644
--- a/chrome/browser/apps/ephemeral_app_service_browsertest.cc
+++ b/chrome/browser/apps/ephemeral_app_service_browsertest.cc
@@ -37,7 +37,7 @@ class EphemeralAppServiceBrowserTest : public PlatformAppBrowserTest {
InstallExtensionWithSourceAndFlags(
path,
1,
- extensions::Manifest::UNPACKED,
+ extensions::Manifest::INTERNAL,
Extension::IS_EPHEMERAL);
app_ids_.push_back(extension->id());
}
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 870b877..182af74 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -35,6 +35,7 @@
#include "chrome/browser/extensions/api/web_request/web_request_api.h"
#include "chrome/browser/extensions/browser_permissions_policy_delegate.h"
#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/extension_web_ui.h"
#include "chrome/browser/extensions/extension_webkit_preferences.h"
#include "chrome/browser/extensions/suggest_permission_util.h"
@@ -785,18 +786,8 @@ void ChromeContentBrowserClient::GetStoragePartitionConfigForSite(
// ExtensionService.
bool is_isolated = !can_be_default;
if (can_be_default) {
- const Extension* extension = NULL;
- Profile* profile = Profile::FromBrowserContext(browser_context);
- ExtensionService* extension_service =
- extensions::ExtensionSystem::Get(profile)->extension_service();
- if (extension_service) {
- extension =
- extension_service->extensions()->GetExtensionOrAppByURL(site);
- if (extension &&
- extensions::AppIsolationInfo::HasIsolatedStorage(extension)) {
- is_isolated = true;
- }
- }
+ if (extensions::util::SiteHasIsolatedStorage(site, browser_context))
+ is_isolated = true;
}
if (is_isolated) {
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 962a050..a578181 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -896,7 +896,10 @@ bool ExtensionService::UninstallExtension(
NOTREACHED();
}
- extensions::DataDeleter::StartDeleting(profile_, extension.get());
+ // Do not remove the data of ephemeral apps. They will be garbage collected by
+ // EphemeralAppService.
+ if (!extension->is_ephemeral())
+ extensions::DataDeleter::StartDeleting(profile_, extension.get());
UntrackTerminatedExtension(extension_id);
@@ -2657,6 +2660,20 @@ void ExtensionService::GarbageCollectIsolatedStorage() {
}
}
+ // The data of ephemeral apps can outlive their cache lifetime. Ensure
+ // they are not garbage collected.
+ scoped_ptr<extensions::ExtensionPrefs::ExtensionsInfo> evicted_apps_info(
+ extension_prefs_->GetEvictedEphemeralAppsInfo());
+ for (size_t i = 0; i < evicted_apps_info->size(); ++i) {
+ extensions::ExtensionInfo* info = evicted_apps_info->at(i).get();
+ if (extensions::util::HasIsolatedStorage(*info)) {
+ active_paths->insert(BrowserContext::GetStoragePartitionForSite(
+ profile_,
+ extensions::util::GetSiteForExtensionId(
+ info->extension_id, profile()))->GetPath());
+ }
+ }
+
DCHECK(!installs_delayed_for_gc());
set_installs_delayed_for_gc(true);
BrowserContext::GarbageCollectStoragePartitions(
diff --git a/chrome/browser/extensions/extension_util.cc b/chrome/browser/extensions/extension_util.cc
index 4faa0dc..2cdd2cc 100644
--- a/chrome/browser/extensions/extension_util.cc
+++ b/chrome/browser/extensions/extension_util.cc
@@ -13,6 +13,7 @@
#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_icon_set.h"
+#include "chrome/common/extensions/manifest_handlers/app_isolation_info.h"
#include "chrome/common/extensions/sync_helper.h"
#include "content/public/browser/site_instance.h"
#include "extensions/browser/extension_prefs.h"
@@ -199,5 +200,45 @@ scoped_ptr<base::DictionaryValue> GetExtensionInfo(const Extension* extension) {
return dict.Pass();
}
+bool HasIsolatedStorage(const ExtensionInfo& info) {
+ if (!info.extension_manifest.get())
+ return false;
+
+ std::string error;
+ scoped_refptr<const Extension> extension(Extension::Create(
+ info.extension_path,
+ info.extension_location,
+ *info.extension_manifest,
+ Extension::NO_FLAGS,
+ info.extension_id,
+ &error));
+ if (!extension.get())
+ return false;
+
+ return AppIsolationInfo::HasIsolatedStorage(extension.get());
+}
+
+bool SiteHasIsolatedStorage(const GURL& extension_site_url,
+ content::BrowserContext* context) {
+ const Extension* extension = ExtensionRegistry::Get(context)->
+ enabled_extensions().GetExtensionOrAppByURL(extension_site_url);
+ if (extension)
+ return AppIsolationInfo::HasIsolatedStorage(extension);
+
+ if (extension_site_url.SchemeIs(kExtensionScheme)) {
+ // The site URL may also be from an evicted ephemeral app. We do not
+ // immediately delete their data when they are removed from extension
+ // system.
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
+ DCHECK(prefs);
+ scoped_ptr<ExtensionInfo> info = prefs->GetEvictedEphemeralAppInfo(
+ extension_site_url.host());
+ if (info.get())
+ return HasIsolatedStorage(*info);
+ }
+
+ return false;
+}
+
} // namespace util
} // namespace extensions
diff --git a/chrome/browser/extensions/extension_util.h b/chrome/browser/extensions/extension_util.h
index 45e3b57..fb3b7ae 100644
--- a/chrome/browser/extensions/extension_util.h
+++ b/chrome/browser/extensions/extension_util.h
@@ -21,6 +21,7 @@ class BrowserContext;
namespace extensions {
class Extension;
+struct ExtensionInfo;
namespace util {
@@ -82,6 +83,14 @@ GURL GetSiteForExtensionId(const std::string& extension_id,
// returned dictionary.
scoped_ptr<base::DictionaryValue> GetExtensionInfo(const Extension* extension);
+// Returns true if the extension has isolated storage.
+bool HasIsolatedStorage(const ExtensionInfo& info);
+
+// Returns true if the site URL corresponds to an extension or app and has
+// isolated storage.
+bool SiteHasIsolatedStorage(const GURL& extension_site_url,
+ content::BrowserContext* context);
+
} // namespace util
} // namespace extensions
diff --git a/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data.pem b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data.pem
new file mode 100644
index 0000000..17f7fac
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data.pem
@@ -0,0 +1,15 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANPxk3C4VXX4xnwrj
+QcPozskZDd0lHEp+Pjl62niY5XTiWrZkYX2At521OeDV1Ylwfweg7uwxpatoylJP8
+6o8X2qJWii9mp2j+LMnkckbt/dkI60xII58EazzPfZhRhzKZxsoE4Ytt2D7zhyRbE
+a7qXyI8S4nD0Qj7w/9ac6lDh/AgMBAAECgYB/v2aNVK4+U1rf0ShKD0TmCwNU4bHv
+m8rzyzHgOpKn5j835jfutN/500p02Re1V0DbhFEGuoCYpcRoyDvrhq03ZWgpidE2e
+8NQ3A0EXYLAUwuOlsdn/jcMEwRtrHpgiXrPMxpn2I0kSRklHYI66KnEvAt6UBvmYG
+5g9NJGQ1Zs0QJBAP+nkuYwh28Av0eglgIs0SLev+D7PemKNHZUoYHcLJM3XAOKbaL
+sb/xm16sdRKvN7qrMIv0jJYiBnLO6qKMCdysCQQDUOuIg664kDK20atzjUsddhBvG
+2czxOsdFaYFHDlZnu9x96rw2xCLbgArg9IypyVw2m5lPYwP7y5iz76Sd49n9AkBvr
+u8XrF+d+H+XdOnTbWy3hQPh7x/u5Ddi8jnUFzFJ5sdFrLWUlSGe6/aPhCu5ui7nYm
+Jun2oIJkckpQiCndMdAkBMd27UR7Z1vK+1iq9NpJy6gAf2DLF/1RrJUqtGq87MD27
+xW2s3HFIm3iqNRO+NmUSFVGjXpqhfx8qcQmMAlIENAkBtde4Pk8q6AZMMPVN+dFEn
+Tr2oJdvvASJ6HSLBWDaGTCOtPIuoCf6TXnfpTkNPE3BBZPspwDbwPyIB+f4D26Ny
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.html b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.html
new file mode 100644
index 0000000..84c6401
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.html
@@ -0,0 +1,11 @@
+<!--
+ * Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+-->
+<!DOCTYPE html>
+<html>
+<body>
+ <script src="index.js"></script>
+</body>
+</html>
diff --git a/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.js b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.js
new file mode 100644
index 0000000..21d21b3
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/index.js
@@ -0,0 +1,180 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var callbackPass = chrome.test.callbackPass;
+var callbackFail = chrome.test.callbackFail;
+
+var kSavedKey = 'ephemeral';
+var kSavedValue = 'app';
+var kTestFileName = 'ephemeral.txt';
+var kTestDBName = 'ephemeral_db';
+
+function FileSystemWriteError() {
+ chrome.test.fail('Filesystem write error');
+}
+
+function FileSystemReadError() {
+ chrome.test.fail('Filesystem read error');
+}
+
+function IndexDBError() {
+ chrome.test.fail('IndexDB error');
+}
+
+function WriteLocalStorage() {
+ var data = {};
+ data[kSavedKey] = kSavedValue;
+ chrome.storage.local.set(data, callbackPass(function() {}));
+}
+
+function WriteFileSystem() {
+ // Testing the existence of a file is sufficient.
+ window.webkitRequestFileSystem(
+ PERSISTENT,
+ 512,
+ callbackPass(function(fs) {
+ fs.root.getFile(
+ kTestFileName,
+ {create: true, exclusive: true},
+ callbackPass(function(fileEntry) {
+ // Succeeded
+ }),
+ FileSystemWriteError);
+ }), FileSystemWriteError);
+}
+
+function WriteIndexedDB() {
+ var openDB = indexedDB.open(kTestDBName, 1);
+ openDB.onerror = IndexDBError;
+
+ openDB.onsuccess = callbackPass(function(e) {
+ var db = e.target.result;
+ var transaction = db.transaction([kTestDBName], 'readwrite');
+ var store = transaction.objectStore(kTestDBName);
+
+ var request = store.add(kSavedValue, kSavedKey);
+ request.onerror = IndexDBError;
+
+ request.onsuccess = callbackPass(function(e) {
+ // Succeeded
+ });
+ });
+
+ openDB.onupgradeneeded = function(e) {
+ var db = e.target.result;
+ db.createObjectStore(kTestDBName);
+ };
+}
+
+function ReadLocalStorage() {
+ chrome.storage.local.get(kSavedKey, callbackPass(function(items) {
+ chrome.test.assertTrue(typeof(items[kSavedKey]) !== 'undefined');
+ chrome.test.assertEq(kSavedValue, items[kSavedKey]);
+ }));
+}
+
+function ReadFileSystem() {
+ window.webkitRequestFileSystem(
+ PERSISTENT,
+ 512,
+ callbackPass(function(fs) {
+ fs.root.getFile(
+ kTestFileName,
+ {},
+ callbackPass(function(fileEntry) {
+ // Succeeded
+ }),
+ FileSystemReadError);
+ }),
+ FileSystemReadError);
+}
+
+function ReadIndexedDB() {
+ var openDB = indexedDB.open(kTestDBName, 1);
+ openDB.onerror = IndexDBError;
+
+ openDB.onsuccess = callbackPass(function(e) {
+ var db = e.target.result;
+ var transaction = db.transaction([kTestDBName], 'readonly');
+ var store = transaction.objectStore(kTestDBName);
+
+ var request = store.get(kSavedKey);
+ request.onerror = IndexDBError;
+
+ request.onsuccess = callbackPass(function(e) {
+ chrome.test.assertEq(kSavedValue, e.target.result);
+ });
+ });
+
+ openDB.onupgradeneeded = function(e) {
+ chrome.test.fail('Indexed DB not initialized');
+ };
+}
+
+function CheckLocalStorageReset() {
+ chrome.storage.local.get(kSavedKey, callbackPass(function(items) {
+ chrome.test.assertEq('undefined', typeof(items[kSavedKey]));
+ }));
+}
+
+function CheckFileSystemReset() {
+ window.webkitRequestFileSystem(
+ PERSISTENT,
+ 512,
+ callbackPass(function(fs) {
+ fs.root.getFile(
+ kTestFileName,
+ {},
+ function(fileEntry) {
+ chrome.test.fail('File ' + kTestFileName + ' should not exist');
+ },
+ callbackPass(function(e) {
+ // Expected failure
+ }));
+ }),
+ FileSystemReadError);
+}
+
+function CheckIndexedDBReset() {
+ var openDB = indexedDB.open(kTestDBName, 1);
+ openDB.onerror = IndexDBError;
+
+ openDB.onsuccess = callbackPass(function(e) {
+ var db = e.target.result;
+ chrome.test.assertFalse(db.objectStoreNames.contains(kTestDBName));
+ });
+}
+
+// Phase 1 - Write data to various storage types.
+function WriteData() {
+ chrome.test.runTests([
+ WriteLocalStorage,
+ WriteFileSystem,
+ WriteIndexedDB
+ ]);
+}
+
+// Phase 2 - Read data back from the various storage types.
+function ReadData() {
+ chrome.test.runTests([
+ ReadLocalStorage,
+ ReadFileSystem,
+ ReadIndexedDB
+ ]);
+}
+
+// Verify that all data has been reset.
+function DataReset() {
+ chrome.test.runTests([
+ CheckLocalStorageReset,
+ CheckFileSystemReset,
+ CheckIndexedDBReset
+ ]);
+}
+
+onload = function() {
+ chrome.test.sendMessage('launched', function(reply) {
+ window[reply]();
+ });
+};
diff --git a/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/main.js b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/main.js
new file mode 100644
index 0000000..f9e810e
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/main.js
@@ -0,0 +1,7 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('index.html', {});
+});
diff --git a/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/manifest.json b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/manifest.json
new file mode 100644
index 0000000..4b9b7ef
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/ephemeral_apps/retain_data/manifest.json
@@ -0,0 +1,12 @@
+{
+ "name": "Ephemeral Apps Retain Data",
+ "version": "1.0",
+ "manifest_version": 2,
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDT8ZNwuFV1+MZ8K40HD6M7JGQ3dJRxKfj45etp4mOV04lq2ZGF9gLedtTng1dWJcH8HoO7sMaWraMpST/OqPF9qiVoovZqdo/izJ5HJG7f3ZCOtMSCOfBGs8z32YUYcymcbKBOGLbdg+84ckWxGu6l8iPEuJw9EI+8P/WnOpQ4fwIDAQAB",
+ "app": {
+ "background": {
+ "scripts": ["main.js"]
+ }
+ },
+ "permissions": ["storage", "unlimitedStorage"]
+}
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index d48a13bb..23513c8 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -179,6 +179,11 @@ const char kPrefGeometryCache[] = "geometry_cache";
// A preference that indicates when an extension is last launched.
const char kPrefLastLaunchTime[] = "last_launch_time";
+// A preference that marks an ephemeral app that was evicted from the cache.
+// Their data is retained and garbage collected when inactive for a long period
+// of time.
+const char kPrefEvictedEphemeralApp[] = "evicted_ephemeral_app";
+
// A list of installed ids and a signature.
const char kInstallSignature[] = "extensions.install_signature";
@@ -223,6 +228,11 @@ bool IsBlacklistBitSet(const base::DictionaryValue* ext) {
return ext->GetBoolean(kPrefBlacklist, &bool_value) && bool_value;
}
+bool IsEvictedEphemeralApp(const base::DictionaryValue* ext) {
+ bool bool_value;
+ return ext->GetBoolean(kPrefEvictedEphemeralApp, &bool_value) && bool_value;
+}
+
} // namespace
//
@@ -1134,7 +1144,14 @@ void ExtensionPrefs::OnExtensionUninstalled(const std::string& extension_id,
extension_pref_value_map_->SetExtensionState(extension_id, false);
content_settings_store_->SetExtensionState(extension_id, false);
} else {
- DeleteExtensionPrefs(extension_id);
+ int creation_flags = GetCreationFlags(extension_id);
+ if (creation_flags & Extension::IS_EPHEMERAL) {
+ // Keep ephemeral apps around, but mark them as evicted.
+ UpdateExtensionPref(extension_id, kPrefEvictedEphemeralApp,
+ new base::FundamentalValue(true));
+ } else {
+ DeleteExtensionPrefs(extension_id);
+ }
}
}
@@ -1267,6 +1284,11 @@ scoped_ptr<ExtensionInfo> ExtensionPrefs::GetInstalledExtensionInfo(
return scoped_ptr<ExtensionInfo>();
}
+ if (IsEvictedEphemeralApp(ext)) {
+ // Hide evicted ephemeral apps.
+ return scoped_ptr<ExtensionInfo>();
+ }
+
return GetInstalledInfoHelper(extension_id, ext);
}
@@ -1439,6 +1461,54 @@ scoped_ptr<ExtensionPrefs::ExtensionsInfo> ExtensionPrefs::
return extensions_info.Pass();
}
+scoped_ptr<ExtensionPrefs::ExtensionsInfo>
+ExtensionPrefs::GetEvictedEphemeralAppsInfo() const {
+ scoped_ptr<ExtensionsInfo> extensions_info(new ExtensionsInfo);
+
+ const base::DictionaryValue* extensions =
+ prefs_->GetDictionary(pref_names::kExtensions);
+ for (base::DictionaryValue::Iterator extension_id(*extensions);
+ !extension_id.IsAtEnd(); extension_id.Advance()) {
+ const base::DictionaryValue* ext = NULL;
+ if (!Extension::IdIsValid(extension_id.key()) ||
+ !extension_id.value().GetAsDictionary(&ext)) {
+ continue;
+ }
+
+ if (!IsEvictedEphemeralApp(ext))
+ continue;
+
+ scoped_ptr<ExtensionInfo> info =
+ GetInstalledInfoHelper(extension_id.key(), ext);
+ if (info)
+ extensions_info->push_back(linked_ptr<ExtensionInfo>(info.release()));
+ }
+
+ return extensions_info.Pass();
+}
+
+scoped_ptr<ExtensionInfo> ExtensionPrefs::GetEvictedEphemeralAppInfo(
+ const std::string& extension_id) const {
+ const base::DictionaryValue* extension_prefs = GetExtensionPref(extension_id);
+ if (!extension_prefs)
+ return scoped_ptr<ExtensionInfo>();
+
+ if (!IsEvictedEphemeralApp(extension_prefs))
+ return scoped_ptr<ExtensionInfo>();
+
+ return GetInstalledInfoHelper(extension_id, extension_prefs);
+}
+
+void ExtensionPrefs::RemoveEvictedEphemeralApp(
+ const std::string& extension_id) {
+ bool evicted_ephemeral_app = false;
+ if (ReadPrefAsBoolean(extension_id,
+ kPrefEvictedEphemeralApp,
+ &evicted_ephemeral_app) && evicted_ephemeral_app) {
+ DeleteExtensionPrefs(extension_id);
+ }
+}
+
bool ExtensionPrefs::WasAppDraggedByUser(const std::string& extension_id) {
return ReadPrefAsBooleanAndReturn(extension_id, kPrefUserDraggedApp);
}
@@ -1907,6 +1977,9 @@ void ExtensionPrefs::FinishExtensionInfoPrefs(
// Clear state that may be registered from a previous install.
extension_dict->Remove(EventRouter::kRegisteredEvents, NULL);
+ // When evicted ephemeral apps are re-installed, this flag must be reset.
+ extension_dict->Remove(kPrefEvictedEphemeralApp, NULL);
+
// FYI, all code below here races on sudden shutdown because |extension_dict|,
// |app_sorting_|, |extension_pref_value_map_|, and |content_settings_store_|
// are updated non-transactionally. This is probably not fixable without
diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h
index 032c578..e288f94 100644
--- a/extensions/browser/extension_prefs.h
+++ b/extensions/browser/extension_prefs.h
@@ -437,6 +437,17 @@ class ExtensionPrefs : public ExtensionScopedPrefs, public KeyedService {
// information.
scoped_ptr<ExtensionsInfo> GetAllDelayedInstallInfo() const;
+ // Returns information about evicted ephemeral apps.
+ scoped_ptr<ExtensionsInfo> GetEvictedEphemeralAppsInfo() const;
+
+ // Return information about a specific evicted ephemeral app. Can return NULL
+ // if no such evicted app exists or is currently installed.
+ scoped_ptr<ExtensionInfo> GetEvictedEphemeralAppInfo(
+ const std::string& extension_id) const;
+
+ // Permanently remove the preferences for an evicted ephemeral app.
+ void RemoveEvictedEphemeralApp(const std::string& extension_id);
+
// Returns true if the user repositioned the app on the app launcher via drag
// and drop.
bool WasAppDraggedByUser(const std::string& extension_id);