[ExtensionToolbarMac] Restrict action button drags to the container's bounds
[chromium-blink-merge.git] / chrome / browser / apps / ephemeral_app_service.cc
blob36c8cdab4a9c7bc7da5201c26f34d09d6533985e
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.
5 #include "chrome/browser/apps/ephemeral_app_service.h"
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"
28 using extensions::Extension;
29 using extensions::ExtensionPrefs;
30 using extensions::ExtensionRegistry;
31 using extensions::ExtensionSet;
32 using extensions::ExtensionSystem;
34 namespace {
36 // The number of seconds after startup before performing garbage collection
37 // of ephemeral apps.
38 const int kGarbageCollectAppsStartupDelay = 60;
40 // The number of seconds after an ephemeral app has been installed before
41 // performing garbage collection.
42 const int kGarbageCollectAppsInstallDelay = 15;
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;
49 // The number of seconds after an app has stopped running before it will be
50 // disabled.
51 const int kDefaultDisableAppDelay = 1;
53 // The number of seconds after startup before disabling inactive ephemeral apps.
54 const int kDisableAppsOnStartupDelay = 5;
56 } // namespace
58 const int EphemeralAppService::kAppInactiveThreshold = 10;
59 const int EphemeralAppService::kAppKeepThreshold = 1;
60 const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
62 // static
63 EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
64 return EphemeralAppServiceFactory::GetForProfile(profile);
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_));
79 EphemeralAppService::~EphemeralAppService() {
82 void EphemeralAppService::ClearCachedApps() {
83 // Cancel any pending garbage collects.
84 garbage_collect_apps_timer_.Stop();
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);
94 scoped_ptr<ExtensionSet> extensions =
95 registry->GenerateInstalledExtensionsSet();
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;
104 // Do not remove apps that are running.
105 if (!extensions::util::IsExtensionIdle(extension_id, profile_))
106 continue;
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);
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;
127 default:
128 NOTREACHED();
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));
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);
164 void EphemeralAppService::OnAppStop(Profile* profile,
165 const std::string& app_id) {
166 if (!extensions::util::IsEphemeralApp(app_id, profile_))
167 return;
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_));
177 void EphemeralAppService::OnChromeTerminating() {
178 garbage_collect_apps_timer_.Stop();
180 extension_registry_observer_.RemoveAll();
181 app_lifetime_monitor_observer_.RemoveAll();
184 void EphemeralAppService::Init() {
185 InitEphemeralAppCount();
187 // Start observing.
188 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
189 app_lifetime_monitor_observer_.Add(
190 apps::AppLifetimeMonitorFactory::GetForProfile(profile_));
192 // Execute startup clean up tasks (except during tests).
193 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType))
194 return;
196 TriggerGarbageCollect(
197 base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
199 base::MessageLoop::current()->PostDelayedTask(
200 FROM_HERE,
201 base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup,
202 weak_ptr_factory_.GetWeakPtr()),
203 base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay));
206 void EphemeralAppService::InitEphemeralAppCount() {
207 scoped_ptr<ExtensionSet> extensions =
208 ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
209 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
210 DCHECK(prefs);
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_;
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;
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);
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);
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;
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;
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);
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);
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);
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);
301 void EphemeralAppService::GarbageCollectApps() {
302 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
303 DCHECK(registry);
304 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
305 DCHECK(prefs);
307 scoped_ptr<ExtensionSet> extensions =
308 registry->GenerateInstalledExtensionsSet();
310 int app_count = 0;
311 LaunchTimeAppMap app_launch_times;
312 std::set<std::string> remove_app_ids;
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;
321 ++app_count;
323 // Do not remove ephemeral apps that are running.
324 if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
325 continue;
327 base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());
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());
335 app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
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);
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;
351 service->UninstallExtension(
352 *id,
353 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
354 base::Bind(&base::DoNothing),
355 NULL);
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);
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;
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;