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/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
{
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
);
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
<
93 BrowserThread::DeleteOnIOThread
>,
94 public quota::StorageObserver
{
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
,
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(
120 base::TimeDelta::FromSeconds(rate
),
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();
133 if (it
->second
.extension_id
== extension_id
) {
134 it
->second
.next_threshold
= next_threshold
;
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
++);
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();
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());
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())
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(
200 base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded
,
202 state
->second
.extension_id
,
203 state
->second
.next_threshold
,
208 OriginStorageStateMap origin_state_map_
;
209 base::WeakPtr
<ExtensionStorageMonitor
> storage_monitor_
;
212 // ExtensionStorageMonitor
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
),
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(
242 const content::NotificationSource
& source
,
243 const content::NotificationDetails
& details
) {
245 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
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
,
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
))
278 if (!enable_for_all_extensions_
) {
279 // If monitoring is not enabled for installed extensions, just stop
281 SetNextStorageThreshold(extension
->id(), 0);
282 StopMonitoringStorage(extension
->id());
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(
297 base::Bind(&StorageEventObserver::UpdateThresholdForExtension
,
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();
321 ExtensionService
* service
=
322 ExtensionSystem::Get(context_
)->extension_service();
324 service
->UninstallExtension(
326 extensions::UNINSTALL_REASON_STORAGE_THRESHOLD_EXCEEDED
,
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
);
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(),
367 void ExtensionStorageMonitor::OnImageLoaded(
368 const std::string
& extension_id
,
370 const gfx::Image
& image
) {
371 const Extension
* extension
= GetExtensionById(context_
, extension_id
);
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()) {
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
,
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
)),
407 base::string16() /* display source */,
408 message_center::NotifierId(
409 message_center::NotifierId::SYSTEM_COMPONENT
, kSystemNotifierId
),
411 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
412 &ExtensionStorageMonitor::OnNotificationButtonClick
,
413 weak_ptr_factory_
.GetWeakPtr(),
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
);
428 case BUTTON_UNINSTALL
: {
429 ShowUninstallPrompt(extension_id
);
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
))
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())) {
459 if (!IsStorageNotificationEnabled(extension
->id()))
462 // Lazily create the storage monitor proxy on the IO thread.
463 if (!storage_observer_
.get()) {
465 new StorageEventObserver(weak_ptr_factory_
.GetWeakPtr());
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(
483 base::Bind(&StorageEventObserver::StartObservingForExtension
,
488 GetNextStorageThreshold(extension
->id()),
492 void ExtensionStorageMonitor::StopMonitoringStorage(
493 const std::string
& extension_id
) {
494 if (!storage_observer_
.get())
497 BrowserThread::PostTask(
500 base::Bind(&StorageEventObserver::StopObservingForExtension
,
505 void ExtensionStorageMonitor::StopMonitoringAll() {
506 extension_registry_observer_
.RemoveAll();
508 RemoveAllNotifications();
510 if (!storage_observer_
.get())
513 BrowserThread::PostTask(
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())
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())
536 message_center::MessageCenter
* center
= message_center::MessageCenter::Get();
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
);
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
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(
578 kPrefNextStorageThreshold
,
580 ? new base::StringValue(base::Int64ToString(next_threshold
))
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
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
;
611 void ExtensionStorageMonitor::SetStorageNotificationEnabled(
612 const std::string
& extension_id
,
613 bool enable_notifications
) {
614 extension_prefs_
->UpdateExtensionPref(
616 kPrefDisableStorageNotifications
,
617 enable_notifications
? NULL
: new base::FundamentalValue(true));
620 } // namespace extensions