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"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_storage_monitor_factory.h"
16 #include "chrome/browser/extensions/extension_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "content/public/browser/browser_context.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/storage_partition.h"
25 #include "extensions/browser/extension_prefs.h"
26 #include "extensions/browser/extension_registry.h"
27 #include "extensions/browser/extension_system.h"
28 #include "extensions/browser/image_loader.h"
29 #include "extensions/browser/uninstall_reason.h"
30 #include "extensions/common/extension.h"
31 #include "extensions/common/manifest_handlers/icons_handler.h"
32 #include "extensions/common/permissions/permissions_data.h"
33 #include "storage/browser/quota/quota_manager.h"
34 #include "storage/browser/quota/storage_observer.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/message_center/message_center.h"
37 #include "ui/message_center/notifier_settings.h"
38 #include "ui/message_center/views/constants.h"
40 using content::BrowserThread
;
42 namespace extensions
{
46 // The rate at which we would like to observe storage events.
47 const int kStorageEventRateSec
= 30;
49 // Set the thresholds for the first notification. Ephemeral apps have a lower
50 // threshold than installed extensions and apps. Once a threshold is exceeded,
51 // it will be doubled to throttle notifications.
52 const int64 kMBytes
= 1024 * 1024;
53 const int64 kEphemeralAppInitialThreshold
= 250 * kMBytes
;
54 const int64 kExtensionInitialThreshold
= 1000 * kMBytes
;
56 // Notifications have an ID so that we can update them.
57 const char kNotificationIdFormat
[] = "ExtensionStorageMonitor-$1-$2";
58 const char kSystemNotifierId
[] = "ExtensionStorageMonitor";
60 // A preference that stores the next threshold for displaying a notification
61 // when an extension or app consumes excessive disk space. This will not be
62 // set until the extension/app reaches the initial threshold.
63 const char kPrefNextStorageThreshold
[] = "next_storage_threshold";
65 // If this preference is set to true, notifications will be suppressed when an
66 // extension or app consumes excessive disk space.
67 const char kPrefDisableStorageNotifications
[] = "disable_storage_notifications";
69 bool ShouldMonitorStorageFor(const Extension
* extension
) {
70 // Only monitor storage for extensions that are granted unlimited storage.
71 // Do not monitor storage for component extensions.
72 return extension
->permissions_data()->HasAPIPermission(
73 APIPermission::kUnlimitedStorage
) &&
74 extension
->location() != Manifest::COMPONENT
;
77 bool ShouldGatherMetricsFor(const Extension
* extension
) {
78 // We want to know the usage of hosted apps' storage.
79 return ShouldMonitorStorageFor(extension
) && extension
->is_hosted_app();
82 const Extension
* GetExtensionById(content::BrowserContext
* context
,
83 const std::string
& extension_id
) {
84 return ExtensionRegistry::Get(context
)->GetExtensionById(
85 extension_id
, ExtensionRegistry::EVERYTHING
);
88 void LogTemporaryStorageUsage(int64 usage
,
89 storage::QuotaStatusCode status
,
91 if (status
== storage::kQuotaStatusOk
) {
93 global_quota
/ storage::QuotaManager::kPerHostTemporaryPortion
;
94 // Note we use COUNTS_100 (instead of PERCENT) because this can potentially
96 UMA_HISTOGRAM_COUNTS_100(
97 "Extensions.HostedAppUnlimitedStorageTemporaryStorageUsage",
98 100.0 * usage
/ per_app_quota
);
104 // StorageEventObserver monitors the storage usage of extensions and lives on
105 // the IO thread. When a threshold is exceeded, a message will be posted to the
106 // UI thread, which displays the notification.
107 class StorageEventObserver
108 : public base::RefCountedThreadSafe
<StorageEventObserver
,
109 BrowserThread::DeleteOnIOThread
>,
110 public storage::StorageObserver
{
112 explicit StorageEventObserver(
113 base::WeakPtr
<ExtensionStorageMonitor
> storage_monitor
)
114 : storage_monitor_(storage_monitor
) {
117 // Register as an observer for the extension's storage events.
118 void StartObservingForExtension(
119 scoped_refptr
<storage::QuotaManager
> quota_manager
,
120 const std::string
& extension_id
,
121 const GURL
& site_url
,
122 int64 next_threshold
,
123 const base::TimeDelta
& rate
,
125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
126 DCHECK(quota_manager
.get());
128 GURL origin
= site_url
.GetOrigin();
129 StorageState
& state
= origin_state_map_
[origin
];
130 state
.quota_manager
= quota_manager
;
131 state
.extension_id
= extension_id
;
132 state
.next_threshold
= next_threshold
;
133 state
.should_uma
= should_uma
;
135 // We always observe persistent storage usage.
136 storage::StorageObserver::MonitorParams
params(
137 storage::kStorageTypePersistent
, origin
, rate
, false);
138 quota_manager
->AddStorageObserver(this, params
);
140 // And if this is for uma, we also observe temporary storage usage.
141 MonitorParams
temporary_params(
142 storage::kStorageTypeTemporary
, origin
, rate
, false);
143 quota_manager
->AddStorageObserver(this, temporary_params
);
147 // Updates the threshold for an extension already being monitored.
148 void UpdateThresholdForExtension(const std::string
& extension_id
,
149 int64 next_threshold
) {
150 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
152 for (OriginStorageStateMap::iterator it
= origin_state_map_
.begin();
153 it
!= origin_state_map_
.end();
155 if (it
->second
.extension_id
== extension_id
) {
156 it
->second
.next_threshold
= next_threshold
;
162 // Deregister as an observer for the extension's storage events.
163 void StopObservingForExtension(const std::string
& extension_id
) {
164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
166 for (OriginStorageStateMap::iterator it
= origin_state_map_
.begin();
167 it
!= origin_state_map_
.end(); ) {
168 if (it
->second
.extension_id
== extension_id
) {
169 storage::StorageObserver::Filter
filter(
170 storage::kStorageTypePersistent
, it
->first
);
171 it
->second
.quota_manager
->RemoveStorageObserverForFilter(this, filter
);
172 // We also need to unregister temporary storage observation, if this was
173 // being tracked for uma.
174 if (it
->second
.should_uma
) {
175 storage::StorageObserver::Filter
temporary_filter(
176 storage::kStorageTypeTemporary
, it
->first
);
177 it
->second
.quota_manager
->RemoveStorageObserverForFilter(this,
180 origin_state_map_
.erase(it
++);
187 // Stop observing all storage events. Called during shutdown.
188 void StopObserving() {
189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
191 for (OriginStorageStateMap::iterator it
= origin_state_map_
.begin();
192 it
!= origin_state_map_
.end(); ++it
) {
193 it
->second
.quota_manager
->RemoveStorageObserver(this);
195 origin_state_map_
.clear();
199 friend class base::DeleteHelper
<StorageEventObserver
>;
200 friend struct content::BrowserThread::DeleteOnThread
<
201 content::BrowserThread::IO
>;
203 struct StorageState
{
204 scoped_refptr
<storage::QuotaManager
> quota_manager
;
206 std::string extension_id
;
208 // If |next_threshold| is -1, it signifies that we should not enforce (and
209 // only track) storage for this extension.
210 int64 next_threshold
;
214 StorageState() : next_threshold(-1), should_uma(false) {}
216 typedef std::map
<GURL
, StorageState
> OriginStorageStateMap
;
218 virtual ~StorageEventObserver() {
219 DCHECK(origin_state_map_
.empty());
223 // storage::StorageObserver implementation.
224 virtual void OnStorageEvent(const Event
& event
) override
{
225 OriginStorageStateMap::iterator iter
=
226 origin_state_map_
.find(event
.filter
.origin
);
227 if (iter
== origin_state_map_
.end())
229 StorageState
& state
= iter
->second
;
231 if (state
.should_uma
) {
232 if (event
.filter
.storage_type
== storage::kStorageTypePersistent
) {
233 UMA_HISTOGRAM_MEMORY_KB(
234 "Extensions.HostedAppUnlimitedStoragePersistentStorageUsage",
237 // We can't use the quota in the event because it assumes unlimited
239 BrowserThread::PostTask(
242 base::Bind(&storage::QuotaManager::GetTemporaryGlobalQuota
,
244 base::Bind(&LogTemporaryStorageUsage
, event
.usage
)));
248 if (state
.next_threshold
!= -1 &&
249 event
.usage
>= state
.next_threshold
) {
250 while (event
.usage
>= state
.next_threshold
)
251 state
.next_threshold
*= 2;
253 BrowserThread::PostTask(
256 base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded
,
259 state
.next_threshold
,
264 OriginStorageStateMap origin_state_map_
;
265 base::WeakPtr
<ExtensionStorageMonitor
> storage_monitor_
;
268 // ExtensionStorageMonitor
271 ExtensionStorageMonitor
* ExtensionStorageMonitor::Get(
272 content::BrowserContext
* context
) {
273 return ExtensionStorageMonitorFactory::GetForBrowserContext(context
);
276 ExtensionStorageMonitor::ExtensionStorageMonitor(
277 content::BrowserContext
* context
)
278 : enable_for_all_extensions_(false),
279 initial_extension_threshold_(kExtensionInitialThreshold
),
280 initial_ephemeral_threshold_(kEphemeralAppInitialThreshold
),
281 observer_rate_(base::TimeDelta::FromSeconds(kStorageEventRateSec
)),
283 extension_prefs_(ExtensionPrefs::Get(context
)),
284 extension_registry_observer_(this),
285 weak_ptr_factory_(this) {
286 DCHECK(extension_prefs_
);
288 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
289 content::Source
<content::BrowserContext
>(context_
));
291 extension_registry_observer_
.Add(ExtensionRegistry::Get(context_
));
294 ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
296 void ExtensionStorageMonitor::Observe(
298 const content::NotificationSource
& source
,
299 const content::NotificationDetails
& details
) {
301 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
310 void ExtensionStorageMonitor::OnExtensionLoaded(
311 content::BrowserContext
* browser_context
,
312 const Extension
* extension
) {
313 StartMonitoringStorage(extension
);
316 void ExtensionStorageMonitor::OnExtensionUnloaded(
317 content::BrowserContext
* browser_context
,
318 const Extension
* extension
,
319 UnloadedExtensionInfo::Reason reason
) {
320 StopMonitoringStorage(extension
->id());
323 void ExtensionStorageMonitor::OnExtensionWillBeInstalled(
324 content::BrowserContext
* browser_context
,
325 const Extension
* extension
,
328 const std::string
& old_name
) {
329 // If an ephemeral app was promoted to a regular installed app, we may need to
330 // increase its next threshold.
331 if (!from_ephemeral
|| !ShouldMonitorStorageFor(extension
))
334 if (!enable_for_all_extensions_
) {
335 // If monitoring is not enabled for installed extensions, just stop
337 SetNextStorageThreshold(extension
->id(), 0);
338 StopMonitoringStorage(extension
->id());
342 int64 next_threshold
= GetNextStorageThresholdFromPrefs(extension
->id());
343 if (next_threshold
<= initial_extension_threshold_
) {
344 // Clear the next threshold in the prefs. This effectively raises it to
345 // |initial_extension_threshold_|. If the current threshold is already
346 // higher than this, leave it as is.
347 SetNextStorageThreshold(extension
->id(), 0);
349 if (storage_observer_
.get()) {
350 BrowserThread::PostTask(
353 base::Bind(&StorageEventObserver::UpdateThresholdForExtension
,
356 initial_extension_threshold_
));
361 void ExtensionStorageMonitor::OnExtensionUninstalled(
362 content::BrowserContext
* browser_context
,
363 const Extension
* extension
,
364 extensions::UninstallReason reason
) {
365 RemoveNotificationForExtension(extension
->id());
368 void ExtensionStorageMonitor::ExtensionUninstallAccepted() {
369 DCHECK(!uninstall_extension_id_
.empty());
371 const Extension
* extension
= GetExtensionById(context_
,
372 uninstall_extension_id_
);
373 uninstall_extension_id_
.clear();
377 ExtensionService
* service
=
378 ExtensionSystem::Get(context_
)->extension_service();
380 service
->UninstallExtension(
382 extensions::UNINSTALL_REASON_STORAGE_THRESHOLD_EXCEEDED
,
383 base::Bind(&base::DoNothing
),
387 void ExtensionStorageMonitor::ExtensionUninstallCanceled() {
388 uninstall_extension_id_
.clear();
391 std::string
ExtensionStorageMonitor::GetNotificationId(
392 const std::string
& extension_id
) {
393 std::vector
<std::string
> placeholders
;
394 placeholders
.push_back(context_
->GetPath().BaseName().MaybeAsASCII());
395 placeholders
.push_back(extension_id
);
397 return ReplaceStringPlaceholders(kNotificationIdFormat
, placeholders
, NULL
);
400 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
401 const std::string
& extension_id
,
402 int64 next_threshold
,
403 int64 current_usage
) {
404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
406 const Extension
* extension
= GetExtensionById(context_
, extension_id
);
410 if (GetNextStorageThreshold(extension
->id()) < next_threshold
)
411 SetNextStorageThreshold(extension
->id(), next_threshold
);
413 const int kIconSize
= message_center::kNotificationIconSize
;
414 ExtensionResource resource
= IconsInfo::GetIconResource(
415 extension
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
416 ImageLoader::Get(context_
)->LoadImageAsync(
417 extension
, resource
, gfx::Size(kIconSize
, kIconSize
),
418 base::Bind(&ExtensionStorageMonitor::OnImageLoaded
,
419 weak_ptr_factory_
.GetWeakPtr(),
424 void ExtensionStorageMonitor::OnImageLoaded(
425 const std::string
& extension_id
,
427 const gfx::Image
& image
) {
428 const Extension
* extension
= GetExtensionById(context_
, extension_id
);
432 // Remove any existing notifications to force a new notification to pop up.
433 std::string
notification_id(GetNotificationId(extension_id
));
434 message_center::MessageCenter::Get()->RemoveNotification(
435 notification_id
, false);
437 message_center::RichNotificationData notification_data
;
438 notification_data
.buttons
.push_back(message_center::ButtonInfo(
439 l10n_util::GetStringUTF16(extension
->is_app() ?
440 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP
:
441 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION
)));
442 notification_data
.buttons
.push_back(message_center::ButtonInfo(
443 l10n_util::GetStringUTF16(extension
->is_app() ?
444 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_APP
:
445 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_EXTENSION
)));
447 gfx::Image
notification_image(image
);
448 if (notification_image
.IsEmpty()) {
450 extension
->is_app() ? gfx::Image(util::GetDefaultAppIcon())
451 : gfx::Image(util::GetDefaultExtensionIcon());
454 scoped_ptr
<message_center::Notification
> notification
;
455 notification
.reset(new message_center::Notification(
456 message_center::NOTIFICATION_TYPE_SIMPLE
,
458 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE
),
459 l10n_util::GetStringFUTF16(
460 IDS_EXTENSION_STORAGE_MONITOR_TEXT
,
461 base::UTF8ToUTF16(extension
->name()),
462 base::IntToString16(current_usage
/ kMBytes
)),
464 base::string16() /* display source */,
465 message_center::NotifierId(
466 message_center::NotifierId::SYSTEM_COMPONENT
, kSystemNotifierId
),
468 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
469 &ExtensionStorageMonitor::OnNotificationButtonClick
,
470 weak_ptr_factory_
.GetWeakPtr(),
472 notification
->SetSystemPriority();
473 message_center::MessageCenter::Get()->AddNotification(notification
.Pass());
475 notified_extension_ids_
.insert(extension_id
);
478 void ExtensionStorageMonitor::OnNotificationButtonClick(
479 const std::string
& extension_id
, int button_index
) {
480 switch (button_index
) {
481 case BUTTON_DISABLE_NOTIFICATION
: {
482 DisableStorageMonitoring(extension_id
);
485 case BUTTON_UNINSTALL
: {
486 ShowUninstallPrompt(extension_id
);
494 void ExtensionStorageMonitor::DisableStorageMonitoring(
495 const std::string
& extension_id
) {
496 scoped_refptr
<const Extension
> extension
=
497 ExtensionRegistry::Get(context_
)->enabled_extensions().GetByID(
499 if (!extension
.get() || !ShouldGatherMetricsFor(extension
.get()))
500 StopMonitoringStorage(extension_id
);
502 SetStorageNotificationEnabled(extension_id
, false);
504 message_center::MessageCenter::Get()->RemoveNotification(
505 GetNotificationId(extension_id
), false);
508 void ExtensionStorageMonitor::StartMonitoringStorage(
509 const Extension
* extension
) {
510 if (!ShouldMonitorStorageFor(extension
))
513 // First apply this feature only to experimental ephemeral apps. If it works
514 // well, roll it out to all extensions and apps.
515 bool should_enforce
=
516 (enable_for_all_extensions_
||
517 extension_prefs_
->IsEphemeralApp(extension
->id())) &&
518 IsStorageNotificationEnabled(extension
->id());
520 bool for_metrics
= ShouldGatherMetricsFor(extension
);
522 if (!should_enforce
&& !for_metrics
)
523 return; // Don't track this extension.
525 // Lazily create the storage monitor proxy on the IO thread.
526 if (!storage_observer_
.get()) {
528 new StorageEventObserver(weak_ptr_factory_
.GetWeakPtr());
531 GURL site_url
= util::GetSiteForExtensionId(extension
->id(), context_
);
532 content::StoragePartition
* storage_partition
=
533 content::BrowserContext::GetStoragePartitionForSite(context_
, site_url
);
534 DCHECK(storage_partition
);
535 scoped_refptr
<storage::QuotaManager
> quota_manager(
536 storage_partition
->GetQuotaManager());
538 GURL
storage_origin(site_url
.GetOrigin());
539 if (extension
->is_hosted_app())
540 storage_origin
= AppLaunchInfo::GetLaunchWebURL(extension
).GetOrigin();
542 // Don't give a threshold if we're not enforcing.
544 should_enforce
? GetNextStorageThreshold(extension
->id()) : -1;
546 BrowserThread::PostTask(
549 base::Bind(&StorageEventObserver::StartObservingForExtension
,
559 void ExtensionStorageMonitor::StopMonitoringStorage(
560 const std::string
& extension_id
) {
561 if (!storage_observer_
.get())
564 BrowserThread::PostTask(
567 base::Bind(&StorageEventObserver::StopObservingForExtension
,
572 void ExtensionStorageMonitor::StopMonitoringAll() {
573 extension_registry_observer_
.RemoveAll();
575 RemoveAllNotifications();
577 if (!storage_observer_
.get())
580 BrowserThread::PostTask(
583 base::Bind(&StorageEventObserver::StopObserving
, storage_observer_
));
584 storage_observer_
= NULL
;
587 void ExtensionStorageMonitor::RemoveNotificationForExtension(
588 const std::string
& extension_id
) {
589 std::set
<std::string
>::iterator ext_id
=
590 notified_extension_ids_
.find(extension_id
);
591 if (ext_id
== notified_extension_ids_
.end())
594 notified_extension_ids_
.erase(ext_id
);
595 message_center::MessageCenter::Get()->RemoveNotification(
596 GetNotificationId(extension_id
), false);
599 void ExtensionStorageMonitor::RemoveAllNotifications() {
600 if (notified_extension_ids_
.empty())
603 message_center::MessageCenter
* center
= message_center::MessageCenter::Get();
605 for (std::set
<std::string
>::iterator it
= notified_extension_ids_
.begin();
606 it
!= notified_extension_ids_
.end(); ++it
) {
607 center
->RemoveNotification(GetNotificationId(*it
), false);
609 notified_extension_ids_
.clear();
612 void ExtensionStorageMonitor::ShowUninstallPrompt(
613 const std::string
& extension_id
) {
614 const Extension
* extension
= GetExtensionById(context_
, extension_id
);
618 if (!uninstall_dialog_
.get()) {
619 uninstall_dialog_
.reset(ExtensionUninstallDialog::Create(
620 Profile::FromBrowserContext(context_
), NULL
, this));
623 uninstall_extension_id_
= extension
->id();
624 uninstall_dialog_
->ConfirmUninstall(extension
);
627 int64
ExtensionStorageMonitor::GetNextStorageThreshold(
628 const std::string
& extension_id
) const {
629 int next_threshold
= GetNextStorageThresholdFromPrefs(extension_id
);
630 if (next_threshold
== 0) {
631 // The next threshold is written to the prefs after the initial threshold is
633 next_threshold
= extension_prefs_
->IsEphemeralApp(extension_id
)
634 ? initial_ephemeral_threshold_
635 : initial_extension_threshold_
;
637 return next_threshold
;
640 void ExtensionStorageMonitor::SetNextStorageThreshold(
641 const std::string
& extension_id
,
642 int64 next_threshold
) {
643 extension_prefs_
->UpdateExtensionPref(
645 kPrefNextStorageThreshold
,
647 ? new base::StringValue(base::Int64ToString(next_threshold
))
651 int64
ExtensionStorageMonitor::GetNextStorageThresholdFromPrefs(
652 const std::string
& extension_id
) const {
653 std::string next_threshold_str
;
654 if (extension_prefs_
->ReadPrefAsString(
655 extension_id
, kPrefNextStorageThreshold
, &next_threshold_str
)) {
656 int64 next_threshold
;
657 if (base::StringToInt64(next_threshold_str
, &next_threshold
))
658 return next_threshold
;
661 // A return value of zero indicates that the initial threshold has not yet
666 bool ExtensionStorageMonitor::IsStorageNotificationEnabled(
667 const std::string
& extension_id
) const {
668 bool disable_notifications
;
669 if (extension_prefs_
->ReadPrefAsBoolean(extension_id
,
670 kPrefDisableStorageNotifications
,
671 &disable_notifications
)) {
672 return !disable_notifications
;
678 void ExtensionStorageMonitor::SetStorageNotificationEnabled(
679 const std::string
& extension_id
,
680 bool enable_notifications
) {
681 extension_prefs_
->UpdateExtensionPref(
683 kPrefDisableStorageNotifications
,
684 enable_notifications
? NULL
: new base::FundamentalValue(true));
687 } // namespace extensions