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_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(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 ~StorageEventObserver() override
{
219 DCHECK(origin_state_map_
.empty());
223 // storage::StorageObserver implementation.
224 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::OnExtensionUninstallDialogClosed(
369 bool did_start_uninstall
,
370 const base::string16
& error
) {
371 // We may get a lagging OnExtensionUninstalledDialogClosed() call during
372 // testing, but did_start_uninstall should be false in this case.
373 DCHECK(!uninstall_extension_id_
.empty() || !did_start_uninstall
);
374 uninstall_extension_id_
.clear();
377 std::string
ExtensionStorageMonitor::GetNotificationId(
378 const std::string
& extension_id
) {
379 std::vector
<std::string
> placeholders
;
380 placeholders
.push_back(context_
->GetPath().BaseName().MaybeAsASCII());
381 placeholders
.push_back(extension_id
);
383 return base::ReplaceStringPlaceholders(
384 kNotificationIdFormat
, placeholders
, NULL
);
387 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
388 const std::string
& extension_id
,
389 int64 next_threshold
,
390 int64 current_usage
) {
391 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
393 const Extension
* extension
= GetExtensionById(context_
, extension_id
);
397 if (GetNextStorageThreshold(extension
->id()) < next_threshold
)
398 SetNextStorageThreshold(extension
->id(), next_threshold
);
400 const int kIconSize
= message_center::kNotificationIconSize
;
401 ExtensionResource resource
= IconsInfo::GetIconResource(
402 extension
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
403 ImageLoader::Get(context_
)->LoadImageAsync(
404 extension
, resource
, gfx::Size(kIconSize
, kIconSize
),
405 base::Bind(&ExtensionStorageMonitor::OnImageLoaded
,
406 weak_ptr_factory_
.GetWeakPtr(),
411 void ExtensionStorageMonitor::OnImageLoaded(
412 const std::string
& extension_id
,
414 const gfx::Image
& image
) {
415 const Extension
* extension
= GetExtensionById(context_
, extension_id
);
419 // Remove any existing notifications to force a new notification to pop up.
420 std::string
notification_id(GetNotificationId(extension_id
));
421 message_center::MessageCenter::Get()->RemoveNotification(
422 notification_id
, false);
424 message_center::RichNotificationData notification_data
;
425 notification_data
.buttons
.push_back(message_center::ButtonInfo(
426 l10n_util::GetStringUTF16(extension
->is_app() ?
427 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP
:
428 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION
)));
429 notification_data
.buttons
.push_back(message_center::ButtonInfo(
430 l10n_util::GetStringUTF16(extension
->is_app() ?
431 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_APP
:
432 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_EXTENSION
)));
434 gfx::Image
notification_image(image
);
435 if (notification_image
.IsEmpty()) {
437 extension
->is_app() ? gfx::Image(util::GetDefaultAppIcon())
438 : gfx::Image(util::GetDefaultExtensionIcon());
441 scoped_ptr
<message_center::Notification
> notification
;
442 notification
.reset(new message_center::Notification(
443 message_center::NOTIFICATION_TYPE_SIMPLE
,
445 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE
),
446 l10n_util::GetStringFUTF16(
447 IDS_EXTENSION_STORAGE_MONITOR_TEXT
,
448 base::UTF8ToUTF16(extension
->name()),
449 base::IntToString16(current_usage
/ kMBytes
)),
451 base::string16() /* display source */,
452 message_center::NotifierId(
453 message_center::NotifierId::SYSTEM_COMPONENT
, kSystemNotifierId
),
455 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
456 &ExtensionStorageMonitor::OnNotificationButtonClick
,
457 weak_ptr_factory_
.GetWeakPtr(),
459 notification
->SetSystemPriority();
460 message_center::MessageCenter::Get()->AddNotification(notification
.Pass());
462 notified_extension_ids_
.insert(extension_id
);
465 void ExtensionStorageMonitor::OnNotificationButtonClick(
466 const std::string
& extension_id
, int button_index
) {
467 switch (button_index
) {
468 case BUTTON_DISABLE_NOTIFICATION
: {
469 DisableStorageMonitoring(extension_id
);
472 case BUTTON_UNINSTALL
: {
473 ShowUninstallPrompt(extension_id
);
481 void ExtensionStorageMonitor::DisableStorageMonitoring(
482 const std::string
& extension_id
) {
483 scoped_refptr
<const Extension
> extension
=
484 ExtensionRegistry::Get(context_
)->enabled_extensions().GetByID(
486 if (!extension
.get() || !ShouldGatherMetricsFor(extension
.get()))
487 StopMonitoringStorage(extension_id
);
489 SetStorageNotificationEnabled(extension_id
, false);
491 message_center::MessageCenter::Get()->RemoveNotification(
492 GetNotificationId(extension_id
), false);
495 void ExtensionStorageMonitor::StartMonitoringStorage(
496 const Extension
* extension
) {
497 if (!ShouldMonitorStorageFor(extension
))
500 // First apply this feature only to experimental ephemeral apps. If it works
501 // well, roll it out to all extensions and apps.
502 bool should_enforce
=
503 (enable_for_all_extensions_
||
504 extension_prefs_
->IsEphemeralApp(extension
->id())) &&
505 IsStorageNotificationEnabled(extension
->id());
507 bool for_metrics
= ShouldGatherMetricsFor(extension
);
509 if (!should_enforce
&& !for_metrics
)
510 return; // Don't track this extension.
512 // Lazily create the storage monitor proxy on the IO thread.
513 if (!storage_observer_
.get()) {
515 new StorageEventObserver(weak_ptr_factory_
.GetWeakPtr());
518 GURL site_url
= util::GetSiteForExtensionId(extension
->id(), context_
);
519 content::StoragePartition
* storage_partition
=
520 content::BrowserContext::GetStoragePartitionForSite(context_
, site_url
);
521 DCHECK(storage_partition
);
522 scoped_refptr
<storage::QuotaManager
> quota_manager(
523 storage_partition
->GetQuotaManager());
525 GURL
storage_origin(site_url
.GetOrigin());
526 if (extension
->is_hosted_app())
527 storage_origin
= AppLaunchInfo::GetLaunchWebURL(extension
).GetOrigin();
529 // Don't give a threshold if we're not enforcing.
531 should_enforce
? GetNextStorageThreshold(extension
->id()) : -1;
533 BrowserThread::PostTask(
536 base::Bind(&StorageEventObserver::StartObservingForExtension
,
546 void ExtensionStorageMonitor::StopMonitoringStorage(
547 const std::string
& extension_id
) {
548 if (!storage_observer_
.get())
551 BrowserThread::PostTask(
554 base::Bind(&StorageEventObserver::StopObservingForExtension
,
559 void ExtensionStorageMonitor::StopMonitoringAll() {
560 extension_registry_observer_
.RemoveAll();
562 RemoveAllNotifications();
564 if (!storage_observer_
.get())
567 BrowserThread::PostTask(
570 base::Bind(&StorageEventObserver::StopObserving
, storage_observer_
));
571 storage_observer_
= NULL
;
574 void ExtensionStorageMonitor::RemoveNotificationForExtension(
575 const std::string
& extension_id
) {
576 std::set
<std::string
>::iterator ext_id
=
577 notified_extension_ids_
.find(extension_id
);
578 if (ext_id
== notified_extension_ids_
.end())
581 notified_extension_ids_
.erase(ext_id
);
582 message_center::MessageCenter::Get()->RemoveNotification(
583 GetNotificationId(extension_id
), false);
586 void ExtensionStorageMonitor::RemoveAllNotifications() {
587 if (notified_extension_ids_
.empty())
590 message_center::MessageCenter
* center
= message_center::MessageCenter::Get();
592 for (std::set
<std::string
>::iterator it
= notified_extension_ids_
.begin();
593 it
!= notified_extension_ids_
.end(); ++it
) {
594 center
->RemoveNotification(GetNotificationId(*it
), false);
596 notified_extension_ids_
.clear();
599 void ExtensionStorageMonitor::ShowUninstallPrompt(
600 const std::string
& extension_id
) {
601 const Extension
* extension
= GetExtensionById(context_
, extension_id
);
605 if (!uninstall_dialog_
.get()) {
606 uninstall_dialog_
.reset(ExtensionUninstallDialog::Create(
607 Profile::FromBrowserContext(context_
), NULL
, this));
610 uninstall_extension_id_
= extension
->id();
611 uninstall_dialog_
->ConfirmUninstall(
612 extension
, extensions::UNINSTALL_REASON_STORAGE_THRESHOLD_EXCEEDED
,
613 UNINSTALL_SOURCE_STORAGE_THRESHOLD_EXCEEDED
);
616 int64
ExtensionStorageMonitor::GetNextStorageThreshold(
617 const std::string
& extension_id
) const {
618 int next_threshold
= GetNextStorageThresholdFromPrefs(extension_id
);
619 if (next_threshold
== 0) {
620 // The next threshold is written to the prefs after the initial threshold is
622 next_threshold
= extension_prefs_
->IsEphemeralApp(extension_id
)
623 ? initial_ephemeral_threshold_
624 : initial_extension_threshold_
;
626 return next_threshold
;
629 void ExtensionStorageMonitor::SetNextStorageThreshold(
630 const std::string
& extension_id
,
631 int64 next_threshold
) {
632 extension_prefs_
->UpdateExtensionPref(
634 kPrefNextStorageThreshold
,
636 ? new base::StringValue(base::Int64ToString(next_threshold
))
640 int64
ExtensionStorageMonitor::GetNextStorageThresholdFromPrefs(
641 const std::string
& extension_id
) const {
642 std::string next_threshold_str
;
643 if (extension_prefs_
->ReadPrefAsString(
644 extension_id
, kPrefNextStorageThreshold
, &next_threshold_str
)) {
645 int64 next_threshold
;
646 if (base::StringToInt64(next_threshold_str
, &next_threshold
))
647 return next_threshold
;
650 // A return value of zero indicates that the initial threshold has not yet
655 bool ExtensionStorageMonitor::IsStorageNotificationEnabled(
656 const std::string
& extension_id
) const {
657 bool disable_notifications
;
658 if (extension_prefs_
->ReadPrefAsBoolean(extension_id
,
659 kPrefDisableStorageNotifications
,
660 &disable_notifications
)) {
661 return !disable_notifications
;
667 void ExtensionStorageMonitor::SetStorageNotificationEnabled(
668 const std::string
& extension_id
,
669 bool enable_notifications
) {
670 extension_prefs_
->UpdateExtensionPref(
672 kPrefDisableStorageNotifications
,
673 enable_notifications
? NULL
: new base::FundamentalValue(true));
676 } // namespace extensions