Revert 285173 "Removed InProcessBrowserTest::CleanUpOnMainThread()"
[chromium-blink-merge.git] / chrome / browser / extensions / extension_storage_monitor.cc
blob8a01ba599d6b7edd4cf2a6a50137fac8f7f432b0
1 // Copyright 2014 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/extensions/extension_storage_monitor.h"
7 #include <map>
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/extension_storage_monitor_factory.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
18 #include "content/public/browser/browser_context.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/notification_details.h"
21 #include "content/public/browser/notification_source.h"
22 #include "content/public/browser/storage_partition.h"
23 #include "extensions/browser/extension_prefs.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/browser/extension_system.h"
26 #include "extensions/browser/image_loader.h"
27 #include "extensions/browser/uninstall_reason.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/manifest_handlers/icons_handler.h"
30 #include "extensions/common/permissions/permissions_data.h"
31 #include "grit/generated_resources.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/message_center/message_center.h"
34 #include "ui/message_center/notifier_settings.h"
35 #include "ui/message_center/views/constants.h"
36 #include "webkit/browser/quota/quota_manager.h"
37 #include "webkit/browser/quota/storage_observer.h"
39 using content::BrowserThread;
41 namespace extensions {
43 namespace {
45 // The rate at which we would like to observe storage events.
46 const int kStorageEventRateSec = 30;
48 // The storage type to monitor.
49 const quota::StorageType kMonitorStorageType = quota::kStorageTypePersistent;
51 // Set the thresholds for the first notification. Ephemeral apps have a lower
52 // threshold than installed extensions and apps. Once a threshold is exceeded,
53 // it will be doubled to throttle notifications.
54 const int64 kMBytes = 1024 * 1024;
55 const int64 kEphemeralAppInitialThreshold = 250 * kMBytes;
56 const int64 kExtensionInitialThreshold = 1000 * kMBytes;
58 // Notifications have an ID so that we can update them.
59 const char kNotificationIdFormat[] = "ExtensionStorageMonitor-$1-$2";
60 const char kSystemNotifierId[] = "ExtensionStorageMonitor";
62 // A preference that stores the next threshold for displaying a notification
63 // when an extension or app consumes excessive disk space. This will not be
64 // set until the extension/app reaches the initial threshold.
65 const char kPrefNextStorageThreshold[] = "next_storage_threshold";
67 // If this preference is set to true, notifications will be suppressed when an
68 // extension or app consumes excessive disk space.
69 const char kPrefDisableStorageNotifications[] = "disable_storage_notifications";
71 bool ShouldMonitorStorageFor(const Extension* extension) {
72 // Only monitor storage for extensions that are granted unlimited storage.
73 // Do not monitor storage for component extensions.
74 return extension->permissions_data()->HasAPIPermission(
75 APIPermission::kUnlimitedStorage) &&
76 extension->location() != Manifest::COMPONENT;
79 const Extension* GetExtensionById(content::BrowserContext* context,
80 const std::string& extension_id) {
81 return ExtensionRegistry::Get(context)->GetExtensionById(
82 extension_id, ExtensionRegistry::EVERYTHING);
85 } // namespace
87 // StorageEventObserver monitors the storage usage of extensions and lives on
88 // the IO thread. When a threshold is exceeded, a message will be posted to the
89 // UI thread, which displays the notification.
90 class StorageEventObserver
91 : public base::RefCountedThreadSafe<
92 StorageEventObserver,
93 BrowserThread::DeleteOnIOThread>,
94 public quota::StorageObserver {
95 public:
96 explicit StorageEventObserver(
97 base::WeakPtr<ExtensionStorageMonitor> storage_monitor)
98 : storage_monitor_(storage_monitor) {
101 // Register as an observer for the extension's storage events.
102 void StartObservingForExtension(
103 scoped_refptr<quota::QuotaManager> quota_manager,
104 const std::string& extension_id,
105 const GURL& site_url,
106 int64 next_threshold,
107 int rate) {
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
109 DCHECK(quota_manager.get());
111 GURL origin = site_url.GetOrigin();
112 StorageState& state = origin_state_map_[origin];
113 state.quota_manager = quota_manager;
114 state.extension_id = extension_id;
115 state.next_threshold = next_threshold;
117 quota::StorageObserver::MonitorParams params(
118 kMonitorStorageType,
119 origin,
120 base::TimeDelta::FromSeconds(rate),
121 false);
122 quota_manager->AddStorageObserver(this, params);
125 // Updates the threshold for an extension already being monitored.
126 void UpdateThresholdForExtension(const std::string& extension_id,
127 int64 next_threshold) {
128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
130 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
131 it != origin_state_map_.end();
132 ++it) {
133 if (it->second.extension_id == extension_id) {
134 it->second.next_threshold = next_threshold;
135 break;
140 // Deregister as an observer for the extension's storage events.
141 void StopObservingForExtension(const std::string& extension_id) {
142 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
144 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
145 it != origin_state_map_.end(); ) {
146 if (it->second.extension_id == extension_id) {
147 quota::StorageObserver::Filter filter(kMonitorStorageType, it->first);
148 it->second.quota_manager->RemoveStorageObserverForFilter(this, filter);
149 origin_state_map_.erase(it++);
150 } else {
151 ++it;
156 // Stop observing all storage events. Called during shutdown.
157 void StopObserving() {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
160 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
161 it != origin_state_map_.end(); ++it) {
162 it->second.quota_manager->RemoveStorageObserver(this);
164 origin_state_map_.clear();
167 private:
168 friend class base::DeleteHelper<StorageEventObserver>;
169 friend struct content::BrowserThread::DeleteOnThread<
170 content::BrowserThread::IO>;
172 struct StorageState {
173 scoped_refptr<quota::QuotaManager> quota_manager;
174 std::string extension_id;
175 int64 next_threshold;
177 StorageState() : next_threshold(0) {}
179 typedef std::map<GURL, StorageState> OriginStorageStateMap;
181 virtual ~StorageEventObserver() {
182 DCHECK(origin_state_map_.empty());
183 StopObserving();
186 // quota::StorageObserver implementation.
187 virtual void OnStorageEvent(const Event& event) OVERRIDE {
188 OriginStorageStateMap::iterator state =
189 origin_state_map_.find(event.filter.origin);
190 if (state == origin_state_map_.end())
191 return;
193 if (event.usage >= state->second.next_threshold) {
194 while (event.usage >= state->second.next_threshold)
195 state->second.next_threshold *= 2;
197 BrowserThread::PostTask(
198 BrowserThread::UI,
199 FROM_HERE,
200 base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded,
201 storage_monitor_,
202 state->second.extension_id,
203 state->second.next_threshold,
204 event.usage));
208 OriginStorageStateMap origin_state_map_;
209 base::WeakPtr<ExtensionStorageMonitor> storage_monitor_;
212 // ExtensionStorageMonitor
214 // static
215 ExtensionStorageMonitor* ExtensionStorageMonitor::Get(
216 content::BrowserContext* context) {
217 return ExtensionStorageMonitorFactory::GetForBrowserContext(context);
220 ExtensionStorageMonitor::ExtensionStorageMonitor(
221 content::BrowserContext* context)
222 : enable_for_all_extensions_(false),
223 initial_extension_threshold_(kExtensionInitialThreshold),
224 initial_ephemeral_threshold_(kEphemeralAppInitialThreshold),
225 observer_rate_(kStorageEventRateSec),
226 context_(context),
227 extension_prefs_(ExtensionPrefs::Get(context)),
228 extension_registry_observer_(this),
229 weak_ptr_factory_(this) {
230 DCHECK(extension_prefs_);
232 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
233 content::Source<content::BrowserContext>(context_));
235 extension_registry_observer_.Add(ExtensionRegistry::Get(context_));
238 ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
240 void ExtensionStorageMonitor::Observe(
241 int type,
242 const content::NotificationSource& source,
243 const content::NotificationDetails& details) {
244 switch (type) {
245 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
246 StopMonitoringAll();
247 break;
249 default:
250 NOTREACHED();
254 void ExtensionStorageMonitor::OnExtensionLoaded(
255 content::BrowserContext* browser_context,
256 const Extension* extension) {
257 StartMonitoringStorage(extension);
260 void ExtensionStorageMonitor::OnExtensionUnloaded(
261 content::BrowserContext* browser_context,
262 const Extension* extension,
263 UnloadedExtensionInfo::Reason reason) {
264 StopMonitoringStorage(extension->id());
267 void ExtensionStorageMonitor::OnExtensionWillBeInstalled(
268 content::BrowserContext* browser_context,
269 const Extension* extension,
270 bool is_update,
271 bool from_ephemeral,
272 const std::string& old_name) {
273 // If an ephemeral app was promoted to a regular installed app, we may need to
274 // increase its next threshold.
275 if (!from_ephemeral || !ShouldMonitorStorageFor(extension))
276 return;
278 if (!enable_for_all_extensions_) {
279 // If monitoring is not enabled for installed extensions, just stop
280 // monitoring.
281 SetNextStorageThreshold(extension->id(), 0);
282 StopMonitoringStorage(extension->id());
283 return;
286 int64 next_threshold = GetNextStorageThresholdFromPrefs(extension->id());
287 if (next_threshold <= initial_extension_threshold_) {
288 // Clear the next threshold in the prefs. This effectively raises it to
289 // |initial_extension_threshold_|. If the current threshold is already
290 // higher than this, leave it as is.
291 SetNextStorageThreshold(extension->id(), 0);
293 if (storage_observer_.get()) {
294 BrowserThread::PostTask(
295 BrowserThread::IO,
296 FROM_HERE,
297 base::Bind(&StorageEventObserver::UpdateThresholdForExtension,
298 storage_observer_,
299 extension->id(),
300 initial_extension_threshold_));
305 void ExtensionStorageMonitor::OnExtensionUninstalled(
306 content::BrowserContext* browser_context,
307 const Extension* extension,
308 extensions::UninstallReason reason) {
309 RemoveNotificationForExtension(extension->id());
312 void ExtensionStorageMonitor::ExtensionUninstallAccepted() {
313 DCHECK(!uninstall_extension_id_.empty());
315 const Extension* extension = GetExtensionById(context_,
316 uninstall_extension_id_);
317 uninstall_extension_id_.clear();
318 if (!extension)
319 return;
321 ExtensionService* service =
322 ExtensionSystem::Get(context_)->extension_service();
323 DCHECK(service);
324 service->UninstallExtension(
325 extension->id(),
326 extensions::UNINSTALL_REASON_STORAGE_THRESHOLD_EXCEEDED,
327 NULL);
330 void ExtensionStorageMonitor::ExtensionUninstallCanceled() {
331 uninstall_extension_id_.clear();
334 std::string ExtensionStorageMonitor::GetNotificationId(
335 const std::string& extension_id) {
336 std::vector<std::string> placeholders;
337 placeholders.push_back(context_->GetPath().BaseName().MaybeAsASCII());
338 placeholders.push_back(extension_id);
340 return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL);
343 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
344 const std::string& extension_id,
345 int64 next_threshold,
346 int64 current_usage) {
347 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349 const Extension* extension = GetExtensionById(context_, extension_id);
350 if (!extension)
351 return;
353 if (GetNextStorageThreshold(extension->id()) < next_threshold)
354 SetNextStorageThreshold(extension->id(), next_threshold);
356 const int kIconSize = message_center::kNotificationIconSize;
357 ExtensionResource resource = IconsInfo::GetIconResource(
358 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
359 ImageLoader::Get(context_)->LoadImageAsync(
360 extension, resource, gfx::Size(kIconSize, kIconSize),
361 base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
362 weak_ptr_factory_.GetWeakPtr(),
363 extension_id,
364 current_usage));
367 void ExtensionStorageMonitor::OnImageLoaded(
368 const std::string& extension_id,
369 int64 current_usage,
370 const gfx::Image& image) {
371 const Extension* extension = GetExtensionById(context_, extension_id);
372 if (!extension)
373 return;
375 // Remove any existing notifications to force a new notification to pop up.
376 std::string notification_id(GetNotificationId(extension_id));
377 message_center::MessageCenter::Get()->RemoveNotification(
378 notification_id, false);
380 message_center::RichNotificationData notification_data;
381 notification_data.buttons.push_back(message_center::ButtonInfo(
382 l10n_util::GetStringUTF16(extension->is_app() ?
383 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
384 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
385 notification_data.buttons.push_back(message_center::ButtonInfo(
386 l10n_util::GetStringUTF16(extension->is_app() ?
387 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_APP :
388 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_EXTENSION)));
390 gfx::Image notification_image(image);
391 if (notification_image.IsEmpty()) {
392 notification_image =
393 extension->is_app() ? gfx::Image(util::GetDefaultAppIcon())
394 : gfx::Image(util::GetDefaultExtensionIcon());
397 scoped_ptr<message_center::Notification> notification;
398 notification.reset(new message_center::Notification(
399 message_center::NOTIFICATION_TYPE_SIMPLE,
400 notification_id,
401 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
402 l10n_util::GetStringFUTF16(
403 IDS_EXTENSION_STORAGE_MONITOR_TEXT,
404 base::UTF8ToUTF16(extension->name()),
405 base::IntToString16(current_usage / kMBytes)),
406 notification_image,
407 base::string16() /* display source */,
408 message_center::NotifierId(
409 message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
410 notification_data,
411 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
412 &ExtensionStorageMonitor::OnNotificationButtonClick,
413 weak_ptr_factory_.GetWeakPtr(),
414 extension_id))));
415 notification->SetSystemPriority();
416 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
418 notified_extension_ids_.insert(extension_id);
421 void ExtensionStorageMonitor::OnNotificationButtonClick(
422 const std::string& extension_id, int button_index) {
423 switch (button_index) {
424 case BUTTON_DISABLE_NOTIFICATION: {
425 DisableStorageMonitoring(extension_id);
426 break;
428 case BUTTON_UNINSTALL: {
429 ShowUninstallPrompt(extension_id);
430 break;
432 default:
433 NOTREACHED();
437 void ExtensionStorageMonitor::DisableStorageMonitoring(
438 const std::string& extension_id) {
439 StopMonitoringStorage(extension_id);
441 SetStorageNotificationEnabled(extension_id, false);
443 message_center::MessageCenter::Get()->RemoveNotification(
444 GetNotificationId(extension_id), false);
447 void ExtensionStorageMonitor::StartMonitoringStorage(
448 const Extension* extension) {
449 if (!ShouldMonitorStorageFor(extension))
450 return;
452 // First apply this feature only to experimental ephemeral apps. If it works
453 // well, roll it out to all extensions and apps.
454 if (!enable_for_all_extensions_ &&
455 !extension_prefs_->IsEphemeralApp(extension->id())) {
456 return;
459 if (!IsStorageNotificationEnabled(extension->id()))
460 return;
462 // Lazily create the storage monitor proxy on the IO thread.
463 if (!storage_observer_.get()) {
464 storage_observer_ =
465 new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
468 GURL site_url =
469 extensions::util::GetSiteForExtensionId(extension->id(), context_);
470 content::StoragePartition* storage_partition =
471 content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
472 DCHECK(storage_partition);
473 scoped_refptr<quota::QuotaManager> quota_manager(
474 storage_partition->GetQuotaManager());
476 GURL storage_origin(site_url.GetOrigin());
477 if (extension->is_hosted_app())
478 storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
480 BrowserThread::PostTask(
481 BrowserThread::IO,
482 FROM_HERE,
483 base::Bind(&StorageEventObserver::StartObservingForExtension,
484 storage_observer_,
485 quota_manager,
486 extension->id(),
487 storage_origin,
488 GetNextStorageThreshold(extension->id()),
489 observer_rate_));
492 void ExtensionStorageMonitor::StopMonitoringStorage(
493 const std::string& extension_id) {
494 if (!storage_observer_.get())
495 return;
497 BrowserThread::PostTask(
498 BrowserThread::IO,
499 FROM_HERE,
500 base::Bind(&StorageEventObserver::StopObservingForExtension,
501 storage_observer_,
502 extension_id));
505 void ExtensionStorageMonitor::StopMonitoringAll() {
506 extension_registry_observer_.RemoveAll();
508 RemoveAllNotifications();
510 if (!storage_observer_.get())
511 return;
513 BrowserThread::PostTask(
514 BrowserThread::IO,
515 FROM_HERE,
516 base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
517 storage_observer_ = NULL;
520 void ExtensionStorageMonitor::RemoveNotificationForExtension(
521 const std::string& extension_id) {
522 std::set<std::string>::iterator ext_id =
523 notified_extension_ids_.find(extension_id);
524 if (ext_id == notified_extension_ids_.end())
525 return;
527 notified_extension_ids_.erase(ext_id);
528 message_center::MessageCenter::Get()->RemoveNotification(
529 GetNotificationId(extension_id), false);
532 void ExtensionStorageMonitor::RemoveAllNotifications() {
533 if (notified_extension_ids_.empty())
534 return;
536 message_center::MessageCenter* center = message_center::MessageCenter::Get();
537 DCHECK(center);
538 for (std::set<std::string>::iterator it = notified_extension_ids_.begin();
539 it != notified_extension_ids_.end(); ++it) {
540 center->RemoveNotification(GetNotificationId(*it), false);
542 notified_extension_ids_.clear();
545 void ExtensionStorageMonitor::ShowUninstallPrompt(
546 const std::string& extension_id) {
547 const Extension* extension = GetExtensionById(context_, extension_id);
548 if (!extension)
549 return;
551 if (!uninstall_dialog_.get()) {
552 uninstall_dialog_.reset(ExtensionUninstallDialog::Create(
553 Profile::FromBrowserContext(context_), NULL, this));
556 uninstall_extension_id_ = extension->id();
557 uninstall_dialog_->ConfirmUninstall(extension);
560 int64 ExtensionStorageMonitor::GetNextStorageThreshold(
561 const std::string& extension_id) const {
562 int next_threshold = GetNextStorageThresholdFromPrefs(extension_id);
563 if (next_threshold == 0) {
564 // The next threshold is written to the prefs after the initial threshold is
565 // exceeded.
566 next_threshold = extension_prefs_->IsEphemeralApp(extension_id)
567 ? initial_ephemeral_threshold_
568 : initial_extension_threshold_;
570 return next_threshold;
573 void ExtensionStorageMonitor::SetNextStorageThreshold(
574 const std::string& extension_id,
575 int64 next_threshold) {
576 extension_prefs_->UpdateExtensionPref(
577 extension_id,
578 kPrefNextStorageThreshold,
579 next_threshold > 0
580 ? new base::StringValue(base::Int64ToString(next_threshold))
581 : NULL);
584 int64 ExtensionStorageMonitor::GetNextStorageThresholdFromPrefs(
585 const std::string& extension_id) const {
586 std::string next_threshold_str;
587 if (extension_prefs_->ReadPrefAsString(
588 extension_id, kPrefNextStorageThreshold, &next_threshold_str)) {
589 int64 next_threshold;
590 if (base::StringToInt64(next_threshold_str, &next_threshold))
591 return next_threshold;
594 // A return value of zero indicates that the initial threshold has not yet
595 // been reached.
596 return 0;
599 bool ExtensionStorageMonitor::IsStorageNotificationEnabled(
600 const std::string& extension_id) const {
601 bool disable_notifications;
602 if (extension_prefs_->ReadPrefAsBoolean(extension_id,
603 kPrefDisableStorageNotifications,
604 &disable_notifications)) {
605 return !disable_notifications;
608 return true;
611 void ExtensionStorageMonitor::SetStorageNotificationEnabled(
612 const std::string& extension_id,
613 bool enable_notifications) {
614 extension_prefs_->UpdateExtensionPref(
615 extension_id,
616 kPrefDisableStorageNotifications,
617 enable_notifications ? NULL : new base::FundamentalValue(true));
620 } // namespace extensions