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_storage_monitor_factory.h"
14 #include "chrome/browser/extensions/extension_util.h"
15 #include "chrome/browser/extensions/image_loader.h"
16 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
17 #include "content/public/browser/browser_context.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/notification_details.h"
20 #include "content/public/browser/notification_source.h"
21 #include "content/public/browser/storage_partition.h"
22 #include "extensions/browser/extension_prefs.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/common/extension.h"
25 #include "extensions/common/manifest_handlers/icons_handler.h"
26 #include "grit/generated_resources.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/message_center/message_center.h"
29 #include "ui/message_center/notifier_settings.h"
30 #include "ui/message_center/views/constants.h"
31 #include "webkit/browser/quota/quota_manager.h"
32 #include "webkit/browser/quota/storage_observer.h"
34 using content::BrowserThread
;
36 namespace extensions
{
40 // The rate at which we would like to observe storage events.
41 const int kStorageEventRateSec
= 30;
43 // The storage type to monitor.
44 const quota::StorageType kMonitorStorageType
= quota::kStorageTypePersistent
;
46 // Set the thresholds for the first notification. Ephemeral apps have a lower
47 // threshold than installed extensions and apps. Once a threshold is exceeded,
48 // it will be doubled to throttle notifications.
49 const int64 kMBytes
= 1024 * 1024;
50 const int64 kEphemeralAppInitialThreshold
= 250 * kMBytes
;
51 const int64 kExtensionInitialThreshold
= 1000 * kMBytes
;
53 // Notifications have an ID so that we can update them.
54 const char kNotificationIdFormat
[] = "ExtensionStorageMonitor-$1-$2";
55 const char kSystemNotifierId
[] = "ExtensionStorageMonitor";
59 // StorageEventObserver monitors the storage usage of extensions and lives on
60 // the IO thread. When a threshold is exceeded, a message will be posted to the
61 // UI thread, which displays the notification.
62 class StorageEventObserver
63 : public base::RefCountedThreadSafe
<
65 BrowserThread::DeleteOnIOThread
>,
66 public quota::StorageObserver
{
68 explicit StorageEventObserver(
69 base::WeakPtr
<ExtensionStorageMonitor
> storage_monitor
)
70 : storage_monitor_(storage_monitor
) {
73 // Register as an observer for the extension's storage events.
74 void StartObservingForExtension(
75 scoped_refptr
<quota::QuotaManager
> quota_manager
,
76 const std::string
& extension_id
,
80 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
81 DCHECK(quota_manager
.get());
83 GURL origin
= site_url
.GetOrigin();
84 StorageState
& state
= origin_state_map_
[origin
];
85 state
.quota_manager
= quota_manager
;
86 state
.extension_id
= extension_id
;
87 state
.next_threshold
= next_threshold
;
89 quota::StorageObserver::MonitorParams
params(
92 base::TimeDelta::FromSeconds(rate
),
94 quota_manager
->AddStorageObserver(this, params
);
97 // Deregister as an observer for the extension's storage events.
98 void StopObservingForExtension(const std::string
& extension_id
) {
99 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
101 for (OriginStorageStateMap::iterator it
= origin_state_map_
.begin();
102 it
!= origin_state_map_
.end(); ) {
103 if (it
->second
.extension_id
== extension_id
) {
104 quota::StorageObserver::Filter
filter(kMonitorStorageType
, it
->first
);
105 it
->second
.quota_manager
->RemoveStorageObserverForFilter(this, filter
);
106 origin_state_map_
.erase(it
++);
113 // Stop observing all storage events. Called during shutdown.
114 void StopObserving() {
115 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
117 for (OriginStorageStateMap::iterator it
= origin_state_map_
.begin();
118 it
!= origin_state_map_
.end(); ++it
) {
119 it
->second
.quota_manager
->RemoveStorageObserver(this);
121 origin_state_map_
.clear();
125 friend class base::DeleteHelper
<StorageEventObserver
>;
126 friend struct content::BrowserThread::DeleteOnThread
<
127 content::BrowserThread::IO
>;
129 struct StorageState
{
130 scoped_refptr
<quota::QuotaManager
> quota_manager
;
131 std::string extension_id
;
132 int64 next_threshold
;
134 StorageState() : next_threshold(0) {}
136 typedef std::map
<GURL
, StorageState
> OriginStorageStateMap
;
138 virtual ~StorageEventObserver() {
139 DCHECK(origin_state_map_
.empty());
143 // quota::StorageObserver implementation.
144 virtual void OnStorageEvent(const Event
& event
) OVERRIDE
{
145 OriginStorageStateMap::iterator state
=
146 origin_state_map_
.find(event
.filter
.origin
);
147 if (state
== origin_state_map_
.end())
150 if (event
.usage
>= state
->second
.next_threshold
) {
151 while (event
.usage
>= state
->second
.next_threshold
)
152 state
->second
.next_threshold
*= 2;
154 BrowserThread::PostTask(
157 base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded
,
159 state
->second
.extension_id
,
160 state
->second
.next_threshold
,
165 OriginStorageStateMap origin_state_map_
;
166 base::WeakPtr
<ExtensionStorageMonitor
> storage_monitor_
;
169 // ExtensionStorageMonitor
172 ExtensionStorageMonitor
* ExtensionStorageMonitor::Get(
173 content::BrowserContext
* context
) {
174 return ExtensionStorageMonitorFactory::GetForBrowserContext(context
);
177 ExtensionStorageMonitor::ExtensionStorageMonitor(
178 content::BrowserContext
* context
)
179 : enable_for_all_extensions_(false),
180 initial_extension_threshold_(kExtensionInitialThreshold
),
181 initial_ephemeral_threshold_(kEphemeralAppInitialThreshold
),
182 observer_rate_(kStorageEventRateSec
),
184 weak_ptr_factory_(this) {
185 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED
,
186 content::Source
<content::BrowserContext
>(context_
));
187 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
188 content::Source
<content::BrowserContext
>(context_
));
190 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context_
);
192 registry
->AddObserver(this);
195 ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
197 void ExtensionStorageMonitor::Observe(
199 const content::NotificationSource
& source
,
200 const content::NotificationDetails
& details
) {
202 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED
: {
203 const Extension
* extension
=
204 content::Details
<const Extension
>(details
).ptr();
205 RemoveNotificationForExtension(extension
->id());
208 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
217 void ExtensionStorageMonitor::OnExtensionLoaded(
218 content::BrowserContext
* browser_context
,
219 const Extension
* extension
) {
221 StartMonitoringStorage(extension
);
224 void ExtensionStorageMonitor::OnExtensionUnloaded(
225 content::BrowserContext
* browser_context
,
226 const Extension
* extension
,
227 UnloadedExtensionInfo::Reason reason
) {
229 StopMonitoringStorage(extension
->id());
232 std::string
ExtensionStorageMonitor::GetNotificationId(
233 const std::string
& extension_id
) {
234 std::vector
<std::string
> placeholders
;
235 placeholders
.push_back(context_
->GetPath().BaseName().MaybeAsASCII());
236 placeholders
.push_back(extension_id
);
238 return ReplaceStringPlaceholders(kNotificationIdFormat
, placeholders
, NULL
);
241 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
242 const std::string
& extension_id
,
243 int64 next_threshold
,
244 int64 current_usage
) {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
247 const Extension
* extension
= ExtensionRegistry::Get(context_
)->
248 GetExtensionById(extension_id
, ExtensionRegistry::EVERYTHING
);
252 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(context_
);
254 prefs
->SetNextStorageThreshold(extension
->id(), next_threshold
);
256 const int kIconSize
= message_center::kNotificationIconSize
;
257 ExtensionResource resource
= IconsInfo::GetIconResource(
258 extension
, kIconSize
, ExtensionIconSet::MATCH_BIGGER
);
259 ImageLoader::Get(context_
)->LoadImageAsync(
260 extension
, resource
, gfx::Size(kIconSize
, kIconSize
),
261 base::Bind(&ExtensionStorageMonitor::OnImageLoaded
,
262 weak_ptr_factory_
.GetWeakPtr(),
267 void ExtensionStorageMonitor::OnImageLoaded(
268 const std::string
& extension_id
,
270 const gfx::Image
& image
) {
271 const Extension
* extension
= ExtensionRegistry::Get(context_
)->
272 GetExtensionById(extension_id
, ExtensionRegistry::EVERYTHING
);
276 // Remove any existing notifications to force a new notification to pop up.
277 std::string
notification_id(GetNotificationId(extension_id
));
278 message_center::MessageCenter::Get()->RemoveNotification(
279 notification_id
, false);
281 message_center::RichNotificationData notification_data
;
282 notification_data
.buttons
.push_back(message_center::ButtonInfo(
283 l10n_util::GetStringUTF16(extension
->is_app() ?
284 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP
:
285 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION
)));
287 gfx::Image
notification_image(image
);
288 if (notification_image
.IsEmpty()) {
290 extension
->is_app() ? gfx::Image(util::GetDefaultAppIcon())
291 : gfx::Image(util::GetDefaultExtensionIcon());
294 scoped_ptr
<message_center::Notification
> notification
;
295 notification
.reset(new message_center::Notification(
296 message_center::NOTIFICATION_TYPE_SIMPLE
,
298 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE
),
299 l10n_util::GetStringFUTF16(
300 IDS_EXTENSION_STORAGE_MONITOR_TEXT
,
301 base::UTF8ToUTF16(extension
->name()),
302 base::IntToString16(current_usage
/ kMBytes
)),
304 base::string16() /* display source */,
305 message_center::NotifierId(
306 message_center::NotifierId::SYSTEM_COMPONENT
, kSystemNotifierId
),
308 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
309 &ExtensionStorageMonitor::OnNotificationButtonClick
,
310 weak_ptr_factory_
.GetWeakPtr(),
312 notification
->SetSystemPriority();
313 message_center::MessageCenter::Get()->AddNotification(notification
.Pass());
315 notified_extension_ids_
.insert(extension_id
);
318 void ExtensionStorageMonitor::OnNotificationButtonClick(
319 const std::string
& extension_id
, int button_index
) {
320 switch (button_index
) {
321 case BUTTON_DISABLE_NOTIFICATION
: {
322 DisableStorageMonitoring(extension_id
);
330 void ExtensionStorageMonitor::DisableStorageMonitoring(
331 const std::string
& extension_id
) {
332 StopMonitoringStorage(extension_id
);
334 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(context_
);
336 prefs
->SetStorageNotificationEnabled(extension_id
, false);
338 message_center::MessageCenter::Get()->RemoveNotification(
339 GetNotificationId(extension_id
), false);
342 void ExtensionStorageMonitor::StartMonitoringStorage(
343 const Extension
* extension
) {
344 if (!extension
->HasAPIPermission(APIPermission::kUnlimitedStorage
))
347 // Do not monitor storage for component extensions.
348 if (extension
->location() == Manifest::COMPONENT
)
351 // First apply this feature only to experimental ephemeral apps. If it works
352 // well, roll it out to all extensions and apps.
353 if (!extension
->is_ephemeral() && !enable_for_all_extensions_
)
356 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(context_
);
358 if (!prefs
->IsStorageNotificationEnabled(extension
->id()))
361 // Lazily create the storage monitor proxy on the IO thread.
362 if (!storage_observer_
.get()) {
364 new StorageEventObserver(weak_ptr_factory_
.GetWeakPtr());
368 extensions::util::GetSiteForExtensionId(extension
->id(), context_
);
369 content::StoragePartition
* storage_partition
=
370 content::BrowserContext::GetStoragePartitionForSite(context_
, site_url
);
371 DCHECK(storage_partition
);
372 scoped_refptr
<quota::QuotaManager
> quota_manager(
373 storage_partition
->GetQuotaManager());
375 GURL
storage_origin(site_url
.GetOrigin());
376 if (extension
->is_hosted_app())
377 storage_origin
= AppLaunchInfo::GetLaunchWebURL(extension
).GetOrigin();
379 int next_threshold
= prefs
->GetNextStorageThreshold(extension
->id());
380 if (next_threshold
== 0) {
381 // The next threshold is written to the prefs after the initial threshold is
383 next_threshold
= extension
->is_ephemeral() ? initial_ephemeral_threshold_
384 : initial_extension_threshold_
;
387 BrowserThread::PostTask(
390 base::Bind(&StorageEventObserver::StartObservingForExtension
,
399 void ExtensionStorageMonitor::StopMonitoringStorage(
400 const std::string
& extension_id
) {
401 if (!storage_observer_
.get())
404 BrowserThread::PostTask(
407 base::Bind(&StorageEventObserver::StopObservingForExtension
,
412 void ExtensionStorageMonitor::StopMonitoringAll() {
413 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context_
);
415 registry
->RemoveObserver(this);
417 RemoveAllNotifications();
419 if (!storage_observer_
.get())
422 BrowserThread::PostTask(
425 base::Bind(&StorageEventObserver::StopObserving
, storage_observer_
));
426 storage_observer_
= NULL
;
429 void ExtensionStorageMonitor::RemoveNotificationForExtension(
430 const std::string
& extension_id
) {
431 std::set
<std::string
>::iterator ext_id
=
432 notified_extension_ids_
.find(extension_id
);
433 if (ext_id
== notified_extension_ids_
.end())
436 notified_extension_ids_
.erase(ext_id
);
437 message_center::MessageCenter::Get()->RemoveNotification(
438 GetNotificationId(extension_id
), false);
441 void ExtensionStorageMonitor::RemoveAllNotifications() {
442 if (notified_extension_ids_
.empty())
445 message_center::MessageCenter
* center
= message_center::MessageCenter::Get();
447 for (std::set
<std::string
>::iterator it
= notified_extension_ids_
.begin();
448 it
!= notified_extension_ids_
.end(); ++it
) {
449 center
->RemoveNotification(GetNotificationId(*it
), false);
451 notified_extension_ids_
.clear();
454 } // namespace extensions