// Copyright 2013 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. #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" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_switches.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #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/notification_types.h" #include "extensions/browser/uninstall_reason.h" #include "extensions/common/extension.h" #include "extensions/common/extension_set.h" using extensions::Extension; using extensions::ExtensionPrefs; using extensions::ExtensionRegistry; using extensions::ExtensionSet; using extensions::ExtensionSystem; namespace { // The number of seconds after startup before performing garbage collection // of ephemeral apps. const int kGarbageCollectAppsStartupDelay = 60; // The number of seconds after an ephemeral app has been installed before // performing garbage collection. 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 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; const int EphemeralAppService::kAppKeepThreshold = 1; const int EphemeralAppService::kMaxEphemeralAppsCount = 30; // static EphemeralAppService* EphemeralAppService::Get(Profile* profile) { return EphemeralAppServiceFactory::GetForProfile(profile); } EphemeralAppService::EphemeralAppService(Profile* profile) : profile_(profile), extension_registry_observer_(this), 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_)); } EphemeralAppService::~EphemeralAppService() { } void EphemeralAppService::ClearCachedApps() { // Cancel any pending garbage collects. garbage_collect_apps_timer_.Stop(); ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); DCHECK(registry); ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); DCHECK(prefs); ExtensionService* service = ExtensionSystem::Get(profile_)->extension_service(); DCHECK(service); scoped_ptr extensions = registry->GenerateInstalledExtensionsSet(); for (ExtensionSet::const_iterator it = extensions->begin(); it != extensions->end(); ++it) { std::string extension_id = (*it)->id(); if (!prefs->IsEphemeralApp(extension_id)) continue; // Do not remove apps that are running. if (!extensions::util::IsExtensionIdle(extension_id, profile_)) continue; DCHECK(registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING)); service->UninstallExtension( extension_id, extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION, base::Bind(&base::DoNothing), NULL); } } void EphemeralAppService::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: { Init(); break; } default: NOTREACHED(); } } void EphemeralAppService::OnExtensionWillBeInstalled( content::BrowserContext* browser_context, const extensions::Extension* extension, bool is_update, bool from_ephemeral, const std::string& old_name) { if (from_ephemeral) { // 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( base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay)); } } } void EphemeralAppService::OnExtensionUninstalled( content::BrowserContext* browser_context, const extensions::Extension* extension, extensions::UninstallReason reason) { if (extensions::util::IsEphemeralApp(extension->id(), profile_)) { --ephemeral_app_count_; DCHECK_GE(ephemeral_app_count_, 0); } } 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() { scoped_ptr extensions = ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet(); ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); DCHECK(prefs); ephemeral_app_count_ = 0; for (ExtensionSet::const_iterator it = extensions->begin(); it != extensions->end(); ++it) { const Extension* extension = *it; if (prefs->IsEphemeralApp(extension->id())) ++ephemeral_app_count_; } } 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 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( FROM_HERE, delay, this, &EphemeralAppService::GarbageCollectApps); } } void EphemeralAppService::GarbageCollectApps() { ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); DCHECK(registry); ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); DCHECK(prefs); scoped_ptr extensions = registry->GenerateInstalledExtensionsSet(); int app_count = 0; LaunchTimeAppMap app_launch_times; std::set remove_app_ids; // Populate a list of ephemeral apps, ordered by their last launch time. for (ExtensionSet::const_iterator it = extensions->begin(); it != extensions->end(); ++it) { const Extension* extension = *it; if (!prefs->IsEphemeralApp(extension->id())) continue; ++app_count; // Do not remove ephemeral apps that are running. if (!extensions::util::IsExtensionIdle(extension->id(), profile_)) continue; base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id()); // If the last launch time is invalid, this may be because it was just // installed. So use the install time. If this is also null for some reason, // the app will be removed. if (last_launch_time.is_null()) last_launch_time = prefs->GetInstallTime(extension->id()); app_launch_times.insert(std::make_pair(last_launch_time, extension->id())); } ExtensionService* service = ExtensionSystem::Get(profile_)->extension_service(); DCHECK(service); // Execute the eviction policies and remove apps marked for deletion. if (!app_launch_times.empty()) { GetAppsToRemove(app_count, app_launch_times, &remove_app_ids); for (std::set::const_iterator id = remove_app_ids.begin(); id != remove_app_ids.end(); ++id) { // Protect against cascading uninstalls. if (!registry->GetExtensionById(*id, ExtensionRegistry::EVERYTHING)) continue; service->UninstallExtension( *id, extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION, base::Bind(&base::DoNothing), NULL); } } } // static void EphemeralAppService::GetAppsToRemove( int app_count, const LaunchTimeAppMap& app_launch_times, std::set* remove_app_ids) { base::Time time_now = base::Time::Now(); const base::Time inactive_threshold = time_now - base::TimeDelta::FromDays(kAppInactiveThreshold); const base::Time keep_threshold = time_now - base::TimeDelta::FromDays(kAppKeepThreshold); // Visit the apps in order of least recently to most recently launched. for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin(); it != app_launch_times.end(); ++it) { // Cannot remove apps that have been launched recently. So break when we // reach the new apps. if (it->first > keep_threshold) break; // Remove ephemeral apps that have been inactive for a while or if the cache // is larger than the desired size. if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) { remove_app_ids->insert(it->second); --app_count; } else { break; } } }