Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / extension_storage_monitor.cc
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.
4
5 #include "chrome/browser/extensions/extension_storage_monitor.h"
6
7 #include <map>
8
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"
38
39 using content::BrowserThread;
40
41 namespace extensions {
42
43 namespace {
44
45 // The rate at which we would like to observe storage events.
46 const int kStorageEventRateSec = 30;
47
48 // The storage type to monitor.
49 const quota::StorageType kMonitorStorageType = quota::kStorageTypePersistent;
50
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;
57
58 // Notifications have an ID so that we can update them.
59 const char kNotificationIdFormat[] = "ExtensionStorageMonitor-$1-$2";
60 const char kSystemNotifierId[] = "ExtensionStorageMonitor";
61
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";
66
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";
70
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;
77 }
78
79 const Extension* GetExtensionById(content::BrowserContext* context,
80                                   const std::string& extension_id) {
81   return ExtensionRegistry::Get(context)->GetExtensionById(
82       extension_id, ExtensionRegistry::EVERYTHING);
83 }
84
85 }  // namespace
86
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) {
99   }
100
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());
110
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;
116
117     quota::StorageObserver::MonitorParams params(
118         kMonitorStorageType,
119         origin,
120         base::TimeDelta::FromSeconds(rate),
121         false);
122     quota_manager->AddStorageObserver(this, params);
123   }
124
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));
129
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;
136       }
137     }
138   }
139
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));
143
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;
152       }
153     }
154   }
155
156   // Stop observing all storage events. Called during shutdown.
157   void StopObserving() {
158     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
159
160     for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
161          it != origin_state_map_.end(); ++it) {
162       it->second.quota_manager->RemoveStorageObserver(this);
163     }
164     origin_state_map_.clear();
165   }
166
167  private:
168   friend class base::DeleteHelper<StorageEventObserver>;
169   friend struct content::BrowserThread::DeleteOnThread<
170       content::BrowserThread::IO>;
171
172   struct StorageState {
173     scoped_refptr<quota::QuotaManager> quota_manager;
174     std::string extension_id;
175     int64 next_threshold;
176
177     StorageState() : next_threshold(0) {}
178   };
179   typedef std::map<GURL, StorageState> OriginStorageStateMap;
180
181   virtual ~StorageEventObserver() {
182     DCHECK(origin_state_map_.empty());
183     StopObserving();
184   }
185
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;
192
193     if (event.usage >= state->second.next_threshold) {
194       while (event.usage >= state->second.next_threshold)
195         state->second.next_threshold *= 2;
196
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));
205     }
206   }
207
208   OriginStorageStateMap origin_state_map_;
209   base::WeakPtr<ExtensionStorageMonitor> storage_monitor_;
210 };
211
212 // ExtensionStorageMonitor
213
214 // static
215 ExtensionStorageMonitor* ExtensionStorageMonitor::Get(
216     content::BrowserContext* context) {
217   return ExtensionStorageMonitorFactory::GetForBrowserContext(context);
218 }
219
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_);
231
232   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
233                  content::Source<content::BrowserContext>(context_));
234
235   extension_registry_observer_.Add(ExtensionRegistry::Get(context_));
236 }
237
238 ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
239
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;
248     }
249     default:
250       NOTREACHED();
251   }
252 }
253
254 void ExtensionStorageMonitor::OnExtensionLoaded(
255     content::BrowserContext* browser_context,
256     const Extension* extension) {
257   StartMonitoringStorage(extension);
258 }
259
260 void ExtensionStorageMonitor::OnExtensionUnloaded(
261     content::BrowserContext* browser_context,
262     const Extension* extension,
263     UnloadedExtensionInfo::Reason reason) {
264   StopMonitoringStorage(extension->id());
265 }
266
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;
277
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;
284   }
285
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);
292
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_));
301     }
302   }
303 }
304
305 void ExtensionStorageMonitor::OnExtensionUninstalled(
306     content::BrowserContext* browser_context,
307     const Extension* extension,
308     extensions::UninstallReason reason) {
309   RemoveNotificationForExtension(extension->id());
310 }
311
312 void ExtensionStorageMonitor::ExtensionUninstallAccepted() {
313   DCHECK(!uninstall_extension_id_.empty());
314
315   const Extension* extension = GetExtensionById(context_,
316                                                 uninstall_extension_id_);
317   uninstall_extension_id_.clear();
318   if (!extension)
319     return;
320
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       base::Bind(&base::DoNothing),
328       NULL);
329 }
330
331 void ExtensionStorageMonitor::ExtensionUninstallCanceled() {
332   uninstall_extension_id_.clear();
333 }
334
335 std::string ExtensionStorageMonitor::GetNotificationId(
336     const std::string& extension_id) {
337   std::vector<std::string> placeholders;
338   placeholders.push_back(context_->GetPath().BaseName().MaybeAsASCII());
339   placeholders.push_back(extension_id);
340
341   return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL);
342 }
343
344 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
345     const std::string& extension_id,
346     int64 next_threshold,
347     int64 current_usage) {
348   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349
350   const Extension* extension = GetExtensionById(context_, extension_id);
351   if (!extension)
352     return;
353
354   if (GetNextStorageThreshold(extension->id()) < next_threshold)
355     SetNextStorageThreshold(extension->id(), next_threshold);
356
357   const int kIconSize = message_center::kNotificationIconSize;
358   ExtensionResource resource =  IconsInfo::GetIconResource(
359       extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
360   ImageLoader::Get(context_)->LoadImageAsync(
361       extension, resource, gfx::Size(kIconSize, kIconSize),
362       base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
363                  weak_ptr_factory_.GetWeakPtr(),
364                  extension_id,
365                  current_usage));
366 }
367
368 void ExtensionStorageMonitor::OnImageLoaded(
369     const std::string& extension_id,
370     int64 current_usage,
371     const gfx::Image& image) {
372   const Extension* extension = GetExtensionById(context_, extension_id);
373   if (!extension)
374     return;
375
376   // Remove any existing notifications to force a new notification to pop up.
377   std::string notification_id(GetNotificationId(extension_id));
378   message_center::MessageCenter::Get()->RemoveNotification(
379       notification_id, false);
380
381   message_center::RichNotificationData notification_data;
382   notification_data.buttons.push_back(message_center::ButtonInfo(
383       l10n_util::GetStringUTF16(extension->is_app() ?
384           IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
385           IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
386   notification_data.buttons.push_back(message_center::ButtonInfo(
387       l10n_util::GetStringUTF16(extension->is_app() ?
388           IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_APP :
389           IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_EXTENSION)));
390
391   gfx::Image notification_image(image);
392   if (notification_image.IsEmpty()) {
393     notification_image =
394         extension->is_app() ? gfx::Image(util::GetDefaultAppIcon())
395                             : gfx::Image(util::GetDefaultExtensionIcon());
396   }
397
398   scoped_ptr<message_center::Notification> notification;
399   notification.reset(new message_center::Notification(
400       message_center::NOTIFICATION_TYPE_SIMPLE,
401       notification_id,
402       l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
403       l10n_util::GetStringFUTF16(
404           IDS_EXTENSION_STORAGE_MONITOR_TEXT,
405           base::UTF8ToUTF16(extension->name()),
406           base::IntToString16(current_usage / kMBytes)),
407       notification_image,
408       base::string16() /* display source */,
409       message_center::NotifierId(
410           message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
411       notification_data,
412       new message_center::HandleNotificationButtonClickDelegate(base::Bind(
413           &ExtensionStorageMonitor::OnNotificationButtonClick,
414           weak_ptr_factory_.GetWeakPtr(),
415           extension_id))));
416   notification->SetSystemPriority();
417   message_center::MessageCenter::Get()->AddNotification(notification.Pass());
418
419   notified_extension_ids_.insert(extension_id);
420 }
421
422 void ExtensionStorageMonitor::OnNotificationButtonClick(
423     const std::string& extension_id, int button_index) {
424   switch (button_index) {
425     case BUTTON_DISABLE_NOTIFICATION: {
426       DisableStorageMonitoring(extension_id);
427       break;
428     }
429     case BUTTON_UNINSTALL: {
430       ShowUninstallPrompt(extension_id);
431       break;
432     }
433     default:
434       NOTREACHED();
435   }
436 }
437
438 void ExtensionStorageMonitor::DisableStorageMonitoring(
439     const std::string& extension_id) {
440   StopMonitoringStorage(extension_id);
441
442   SetStorageNotificationEnabled(extension_id, false);
443
444   message_center::MessageCenter::Get()->RemoveNotification(
445       GetNotificationId(extension_id), false);
446 }
447
448 void ExtensionStorageMonitor::StartMonitoringStorage(
449     const Extension* extension) {
450   if (!ShouldMonitorStorageFor(extension))
451     return;
452
453   // First apply this feature only to experimental ephemeral apps. If it works
454   // well, roll it out to all extensions and apps.
455   if (!enable_for_all_extensions_ &&
456       !extension_prefs_->IsEphemeralApp(extension->id())) {
457     return;
458   }
459
460   if (!IsStorageNotificationEnabled(extension->id()))
461     return;
462
463   // Lazily create the storage monitor proxy on the IO thread.
464   if (!storage_observer_.get()) {
465     storage_observer_ =
466         new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
467   }
468
469   GURL site_url =
470       extensions::util::GetSiteForExtensionId(extension->id(), context_);
471   content::StoragePartition* storage_partition =
472       content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
473   DCHECK(storage_partition);
474   scoped_refptr<quota::QuotaManager> quota_manager(
475       storage_partition->GetQuotaManager());
476
477   GURL storage_origin(site_url.GetOrigin());
478   if (extension->is_hosted_app())
479     storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
480
481   BrowserThread::PostTask(
482       BrowserThread::IO,
483       FROM_HERE,
484       base::Bind(&StorageEventObserver::StartObservingForExtension,
485                  storage_observer_,
486                  quota_manager,
487                  extension->id(),
488                  storage_origin,
489                  GetNextStorageThreshold(extension->id()),
490                  observer_rate_));
491 }
492
493 void ExtensionStorageMonitor::StopMonitoringStorage(
494     const std::string& extension_id) {
495   if (!storage_observer_.get())
496     return;
497
498   BrowserThread::PostTask(
499       BrowserThread::IO,
500       FROM_HERE,
501       base::Bind(&StorageEventObserver::StopObservingForExtension,
502                  storage_observer_,
503                  extension_id));
504 }
505
506 void ExtensionStorageMonitor::StopMonitoringAll() {
507   extension_registry_observer_.RemoveAll();
508
509   RemoveAllNotifications();
510
511   if (!storage_observer_.get())
512     return;
513
514   BrowserThread::PostTask(
515       BrowserThread::IO,
516       FROM_HERE,
517       base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
518   storage_observer_ = NULL;
519 }
520
521 void ExtensionStorageMonitor::RemoveNotificationForExtension(
522     const std::string& extension_id) {
523   std::set<std::string>::iterator ext_id =
524       notified_extension_ids_.find(extension_id);
525   if (ext_id == notified_extension_ids_.end())
526     return;
527
528   notified_extension_ids_.erase(ext_id);
529   message_center::MessageCenter::Get()->RemoveNotification(
530       GetNotificationId(extension_id), false);
531 }
532
533 void ExtensionStorageMonitor::RemoveAllNotifications() {
534   if (notified_extension_ids_.empty())
535     return;
536
537   message_center::MessageCenter* center = message_center::MessageCenter::Get();
538   DCHECK(center);
539   for (std::set<std::string>::iterator it = notified_extension_ids_.begin();
540        it != notified_extension_ids_.end(); ++it) {
541     center->RemoveNotification(GetNotificationId(*it), false);
542   }
543   notified_extension_ids_.clear();
544 }
545
546 void ExtensionStorageMonitor::ShowUninstallPrompt(
547     const std::string& extension_id) {
548   const Extension* extension = GetExtensionById(context_, extension_id);
549   if (!extension)
550     return;
551
552   if (!uninstall_dialog_.get()) {
553     uninstall_dialog_.reset(ExtensionUninstallDialog::Create(
554         Profile::FromBrowserContext(context_), NULL, this));
555   }
556
557   uninstall_extension_id_ = extension->id();
558   uninstall_dialog_->ConfirmUninstall(extension);
559 }
560
561 int64 ExtensionStorageMonitor::GetNextStorageThreshold(
562     const std::string& extension_id) const {
563   int next_threshold = GetNextStorageThresholdFromPrefs(extension_id);
564   if (next_threshold == 0) {
565     // The next threshold is written to the prefs after the initial threshold is
566     // exceeded.
567     next_threshold = extension_prefs_->IsEphemeralApp(extension_id)
568                          ? initial_ephemeral_threshold_
569                          : initial_extension_threshold_;
570   }
571   return next_threshold;
572 }
573
574 void ExtensionStorageMonitor::SetNextStorageThreshold(
575     const std::string& extension_id,
576     int64 next_threshold) {
577   extension_prefs_->UpdateExtensionPref(
578       extension_id,
579       kPrefNextStorageThreshold,
580       next_threshold > 0
581           ? new base::StringValue(base::Int64ToString(next_threshold))
582           : NULL);
583 }
584
585 int64 ExtensionStorageMonitor::GetNextStorageThresholdFromPrefs(
586     const std::string& extension_id) const {
587   std::string next_threshold_str;
588   if (extension_prefs_->ReadPrefAsString(
589           extension_id, kPrefNextStorageThreshold, &next_threshold_str)) {
590     int64 next_threshold;
591     if (base::StringToInt64(next_threshold_str, &next_threshold))
592       return next_threshold;
593   }
594
595   // A return value of zero indicates that the initial threshold has not yet
596   // been reached.
597   return 0;
598 }
599
600 bool ExtensionStorageMonitor::IsStorageNotificationEnabled(
601     const std::string& extension_id) const {
602   bool disable_notifications;
603   if (extension_prefs_->ReadPrefAsBoolean(extension_id,
604                                           kPrefDisableStorageNotifications,
605                                           &disable_notifications)) {
606     return !disable_notifications;
607   }
608
609   return true;
610 }
611
612 void ExtensionStorageMonitor::SetStorageNotificationEnabled(
613     const std::string& extension_id,
614     bool enable_notifications) {
615   extension_prefs_->UpdateExtensionPref(
616       extension_id,
617       kPrefDisableStorageNotifications,
618       enable_notifications ? NULL : new base::FundamentalValue(true));
619 }
620
621 }  // namespace extensions