1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/apps/ephemeral_app_service.h" 6 7 #include "apps/app_lifetime_monitor_factory.h" 8 #include "base/command_line.h" 9 #include "base/message_loop/message_loop.h" 10 #include "chrome/browser/apps/ephemeral_app_service_factory.h" 11 #include "chrome/browser/chrome_notification_types.h" 12 #include "chrome/browser/extensions/extension_service.h" 13 #include "chrome/browser/extensions/extension_util.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/common/chrome_switches.h" 16 #include "content/public/browser/notification_service.h" 17 #include "content/public/browser/notification_source.h" 18 #include "content/public/browser/notification_types.h" 19 #include "extensions/browser/extension_prefs.h" 20 #include "extensions/browser/extension_registry.h" 21 #include "extensions/browser/extension_system.h" 22 #include "extensions/browser/extension_util.h" 23 #include "extensions/browser/notification_types.h" 24 #include "extensions/browser/uninstall_reason.h" 25 #include "extensions/common/extension.h" 26 #include "extensions/common/extension_set.h" 27 28 using extensions::Extension; 29 using extensions::ExtensionPrefs; 30 using extensions::ExtensionRegistry; 31 using extensions::ExtensionSet; 32 using extensions::ExtensionSystem; 33 34 namespace { 35 36 // The number of seconds after startup before performing garbage collection 37 // of ephemeral apps. 38 const int kGarbageCollectAppsStartupDelay = 60; 39 40 // The number of seconds after an ephemeral app has been installed before 41 // performing garbage collection. 42 const int kGarbageCollectAppsInstallDelay = 15; 43 44 // When the number of ephemeral apps reaches this count, trigger garbage 45 // collection to trim off the least-recently used apps in excess of 46 // kMaxEphemeralAppsCount. 47 const int kGarbageCollectAppsTriggerCount = 35; 48 49 // The number of seconds after an app has stopped running before it will be 50 // disabled. 51 const int kDefaultDisableAppDelay = 1; 52 53 // The number of seconds after startup before disabling inactive ephemeral apps. 54 const int kDisableAppsOnStartupDelay = 5; 55 56 } // namespace 57 58 const int EphemeralAppService::kAppInactiveThreshold = 10; 59 const int EphemeralAppService::kAppKeepThreshold = 1; 60 const int EphemeralAppService::kMaxEphemeralAppsCount = 30; 61 62 // static 63 EphemeralAppService* EphemeralAppService::Get(Profile* profile) { 64 return EphemeralAppServiceFactory::GetForProfile(profile); 65 } 66 67 EphemeralAppService::EphemeralAppService(Profile* profile) 68 : profile_(profile), 69 extension_registry_observer_(this), 70 app_lifetime_monitor_observer_(this), 71 ephemeral_app_count_(-1), 72 disable_idle_app_delay_(kDefaultDisableAppDelay), 73 weak_ptr_factory_(this) { 74 registrar_.Add(this, 75 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, 76 content::Source<Profile>(profile_)); 77 } 78 79 EphemeralAppService::~EphemeralAppService() { 80 } 81 82 void EphemeralAppService::ClearCachedApps() { 83 // Cancel any pending garbage collects. 84 garbage_collect_apps_timer_.Stop(); 85 86 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); 87 DCHECK(registry); 88 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 89 DCHECK(prefs); 90 ExtensionService* service = 91 ExtensionSystem::Get(profile_)->extension_service(); 92 DCHECK(service); 93 94 scoped_ptr<ExtensionSet> extensions = 95 registry->GenerateInstalledExtensionsSet(); 96 97 for (ExtensionSet::const_iterator it = extensions->begin(); 98 it != extensions->end(); 99 ++it) { 100 std::string extension_id = (*it)->id(); 101 if (!prefs->IsEphemeralApp(extension_id)) 102 continue; 103 104 // Do not remove apps that are running. 105 if (!extensions::util::IsExtensionIdle(extension_id, profile_)) 106 continue; 107 108 DCHECK(registry->GetExtensionById(extension_id, 109 ExtensionRegistry::EVERYTHING)); 110 service->UninstallExtension( 111 extension_id, 112 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION, 113 base::Bind(&base::DoNothing), 114 NULL); 115 } 116 } 117 118 void EphemeralAppService::Observe( 119 int type, 120 const content::NotificationSource& source, 121 const content::NotificationDetails& details) { 122 switch (type) { 123 case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: { 124 Init(); 125 break; 126 } 127 default: 128 NOTREACHED(); 129 } 130 } 131 132 void EphemeralAppService::OnExtensionWillBeInstalled( 133 content::BrowserContext* browser_context, 134 const extensions::Extension* extension, 135 bool is_update, 136 bool from_ephemeral, 137 const std::string& old_name) { 138 if (from_ephemeral) { 139 // An ephemeral app was just promoted to a regular installed app. 140 --ephemeral_app_count_; 141 DCHECK_GE(ephemeral_app_count_, 0); 142 HandleEphemeralAppPromoted(extension); 143 } else if (!is_update && 144 extensions::util::IsEphemeralApp(extension->id(), profile_)) { 145 // A new ephemeral app was launched. 146 ++ephemeral_app_count_; 147 if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) { 148 TriggerGarbageCollect( 149 base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay)); 150 } 151 } 152 } 153 154 void EphemeralAppService::OnExtensionUninstalled( 155 content::BrowserContext* browser_context, 156 const extensions::Extension* extension, 157 extensions::UninstallReason reason) { 158 if (extensions::util::IsEphemeralApp(extension->id(), profile_)) { 159 --ephemeral_app_count_; 160 DCHECK_GE(ephemeral_app_count_, 0); 161 } 162 } 163 164 void EphemeralAppService::OnAppStop(Profile* profile, 165 const std::string& app_id) { 166 if (!extensions::util::IsEphemeralApp(app_id, profile_)) 167 return; 168 169 base::MessageLoop::current()->PostDelayedTask( 170 FROM_HERE, 171 base::Bind(&EphemeralAppService::DisableEphemeralApp, 172 weak_ptr_factory_.GetWeakPtr(), 173 app_id), 174 base::TimeDelta::FromSeconds(disable_idle_app_delay_)); 175 } 176 177 void EphemeralAppService::OnChromeTerminating() { 178 garbage_collect_apps_timer_.Stop(); 179 180 extension_registry_observer_.RemoveAll(); 181 app_lifetime_monitor_observer_.RemoveAll(); 182 } 183 184 void EphemeralAppService::Init() { 185 InitEphemeralAppCount(); 186 187 // Start observing. 188 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); 189 app_lifetime_monitor_observer_.Add( 190 apps::AppLifetimeMonitorFactory::GetForProfile(profile_)); 191 192 // Execute startup clean up tasks (except during tests). 193 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) 194 return; 195 196 TriggerGarbageCollect( 197 base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay)); 198 199 base::MessageLoop::current()->PostDelayedTask( 200 FROM_HERE, 201 base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup, 202 weak_ptr_factory_.GetWeakPtr()), 203 base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay)); 204 } 205 206 void EphemeralAppService::InitEphemeralAppCount() { 207 scoped_ptr<ExtensionSet> extensions = 208 ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet(); 209 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 210 DCHECK(prefs); 211 212 ephemeral_app_count_ = 0; 213 for (ExtensionSet::const_iterator it = extensions->begin(); 214 it != extensions->end(); ++it) { 215 const Extension* extension = it->get(); 216 if (prefs->IsEphemeralApp(extension->id())) 217 ++ephemeral_app_count_; 218 } 219 } 220 221 void EphemeralAppService::DisableEphemeralApp(const std::string& app_id) { 222 if (!extensions::util::IsEphemeralApp(app_id, profile_) || 223 !extensions::util::IsExtensionIdle(app_id, profile_)) { 224 return; 225 } 226 227 // After an ephemeral app has stopped running, unload it from extension 228 // system and disable it to prevent all background activity. 229 ExtensionService* service = 230 ExtensionSystem::Get(profile_)->extension_service(); 231 DCHECK(service); 232 service->DisableExtension(app_id, Extension::DISABLE_INACTIVE_EPHEMERAL_APP); 233 } 234 235 void EphemeralAppService::DisableEphemeralAppsOnStartup() { 236 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 237 DCHECK(prefs); 238 ExtensionService* service = 239 ExtensionSystem::Get(profile_)->extension_service(); 240 DCHECK(service); 241 242 // Ensure that all inactive ephemeral apps are disabled to prevent all 243 // background activity. This is done on startup to catch any apps that escaped 244 // being disabled on shutdown. 245 scoped_ptr<ExtensionSet> extensions = 246 ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet(); 247 for (ExtensionSet::const_iterator it = extensions->begin(); 248 it != extensions->end(); 249 ++it) { 250 const Extension* extension = it->get(); 251 if (!prefs->IsEphemeralApp(extension->id())) 252 continue; 253 254 // Only V2 apps are installed ephemerally. Remove other ephemeral app types 255 // that were cached before this policy was introduced. 256 if (!extension->is_platform_app()) { 257 service->UninstallExtension( 258 extension->id(), 259 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION, 260 base::Bind(&base::DoNothing), 261 NULL); 262 continue; 263 } 264 265 if (!prefs->HasDisableReason(extension->id(), 266 Extension::DISABLE_INACTIVE_EPHEMERAL_APP) && 267 !prefs->IsExtensionRunning(extension->id()) && 268 extensions::util::IsExtensionIdle(extension->id(), profile_)) { 269 service->DisableExtension(extension->id(), 270 Extension::DISABLE_INACTIVE_EPHEMERAL_APP); 271 } 272 } 273 } 274 275 void EphemeralAppService::HandleEphemeralAppPromoted(const Extension* app) { 276 // When ephemeral apps are promoted to regular install apps, remove the 277 // DISABLE_INACTIVE_EPHEMERAL_APP reason and enable the app if there are no 278 // other reasons. 279 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 280 DCHECK(prefs); 281 282 int disable_reasons = prefs->GetDisableReasons(app->id()); 283 if (disable_reasons & Extension::DISABLE_INACTIVE_EPHEMERAL_APP) { 284 prefs->RemoveDisableReason(app->id(), 285 Extension::DISABLE_INACTIVE_EPHEMERAL_APP); 286 if (disable_reasons == Extension::DISABLE_INACTIVE_EPHEMERAL_APP) 287 prefs->SetExtensionState(app->id(), Extension::ENABLED); 288 } 289 } 290 291 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) { 292 if (!garbage_collect_apps_timer_.IsRunning()) { 293 garbage_collect_apps_timer_.Start( 294 FROM_HERE, 295 delay, 296 this, 297 &EphemeralAppService::GarbageCollectApps); 298 } 299 } 300 301 void EphemeralAppService::GarbageCollectApps() { 302 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); 303 DCHECK(registry); 304 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 305 DCHECK(prefs); 306 307 scoped_ptr<ExtensionSet> extensions = 308 registry->GenerateInstalledExtensionsSet(); 309 310 int app_count = 0; 311 LaunchTimeAppMap app_launch_times; 312 std::set<std::string> remove_app_ids; 313 314 // Populate a list of ephemeral apps, ordered by their last launch time. 315 for (ExtensionSet::const_iterator it = extensions->begin(); 316 it != extensions->end(); ++it) { 317 const Extension* extension = it->get(); 318 if (!prefs->IsEphemeralApp(extension->id())) 319 continue; 320 321 ++app_count; 322 323 // Do not remove ephemeral apps that are running. 324 if (!extensions::util::IsExtensionIdle(extension->id(), profile_)) 325 continue; 326 327 base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id()); 328 329 // If the last launch time is invalid, this may be because it was just 330 // installed. So use the install time. If this is also null for some reason, 331 // the app will be removed. 332 if (last_launch_time.is_null()) 333 last_launch_time = prefs->GetInstallTime(extension->id()); 334 335 app_launch_times.insert(std::make_pair(last_launch_time, extension->id())); 336 } 337 338 ExtensionService* service = 339 ExtensionSystem::Get(profile_)->extension_service(); 340 DCHECK(service); 341 // Execute the eviction policies and remove apps marked for deletion. 342 if (!app_launch_times.empty()) { 343 GetAppsToRemove(app_count, app_launch_times, &remove_app_ids); 344 345 for (std::set<std::string>::const_iterator id = remove_app_ids.begin(); 346 id != remove_app_ids.end(); ++id) { 347 // Protect against cascading uninstalls. 348 if (!registry->GetExtensionById(*id, ExtensionRegistry::EVERYTHING)) 349 continue; 350 351 service->UninstallExtension( 352 *id, 353 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION, 354 base::Bind(&base::DoNothing), 355 NULL); 356 } 357 } 358 } 359 360 // static 361 void EphemeralAppService::GetAppsToRemove( 362 int app_count, 363 const LaunchTimeAppMap& app_launch_times, 364 std::set<std::string>* remove_app_ids) { 365 base::Time time_now = base::Time::Now(); 366 const base::Time inactive_threshold = 367 time_now - base::TimeDelta::FromDays(kAppInactiveThreshold); 368 const base::Time keep_threshold = 369 time_now - base::TimeDelta::FromDays(kAppKeepThreshold); 370 371 // Visit the apps in order of least recently to most recently launched. 372 for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin(); 373 it != app_launch_times.end(); ++it) { 374 // Cannot remove apps that have been launched recently. So break when we 375 // reach the new apps. 376 if (it->first > keep_threshold) 377 break; 378 379 // Remove ephemeral apps that have been inactive for a while or if the cache 380 // is larger than the desired size. 381 if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) { 382 remove_app_ids->insert(it->second); 383 --app_count; 384 } else { 385 break; 386 } 387 } 388 } 389