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 "chrome/grit/generated_resources.h"
19#include "content/public/browser/browser_context.h"
20#include "content/public/browser/browser_thread.h"
21#include "content/public/browser/notification_details.h"
22#include "content/public/browser/notification_source.h"
23#include "content/public/browser/storage_partition.h"
24#include "extensions/browser/extension_prefs.h"
25#include "extensions/browser/extension_registry.h"
26#include "extensions/browser/extension_system.h"
27#include "extensions/browser/image_loader.h"
28#include "extensions/browser/uninstall_reason.h"
29#include "extensions/common/extension.h"
30#include "extensions/common/manifest_handlers/icons_handler.h"
31#include "extensions/common/permissions/permissions_data.h"
32#include "storage/browser/quota/quota_manager.h"
33#include "storage/browser/quota/storage_observer.h"
34#include "ui/base/l10n/l10n_util.h"
35#include "ui/message_center/message_center.h"
36#include "ui/message_center/notifier_settings.h"
37#include "ui/message_center/views/constants.h"
38
39using content::BrowserThread;
40
41namespace extensions {
42
43namespace {
44
45// The rate at which we would like to observe storage events.
46const int kStorageEventRateSec = 30;
47
48// The storage type to monitor.
49const storage::StorageType kMonitorStorageType =
50    storage::kStorageTypePersistent;
51
52// Set the thresholds for the first notification. Ephemeral apps have a lower
53// threshold than installed extensions and apps. Once a threshold is exceeded,
54// it will be doubled to throttle notifications.
55const int64 kMBytes = 1024 * 1024;
56const int64 kEphemeralAppInitialThreshold = 250 * kMBytes;
57const int64 kExtensionInitialThreshold = 1000 * kMBytes;
58
59// Notifications have an ID so that we can update them.
60const char kNotificationIdFormat[] = "ExtensionStorageMonitor-$1-$2";
61const char kSystemNotifierId[] = "ExtensionStorageMonitor";
62
63// A preference that stores the next threshold for displaying a notification
64// when an extension or app consumes excessive disk space. This will not be
65// set until the extension/app reaches the initial threshold.
66const char kPrefNextStorageThreshold[] = "next_storage_threshold";
67
68// If this preference is set to true, notifications will be suppressed when an
69// extension or app consumes excessive disk space.
70const char kPrefDisableStorageNotifications[] = "disable_storage_notifications";
71
72bool ShouldMonitorStorageFor(const Extension* extension) {
73  // Only monitor storage for extensions that are granted unlimited storage.
74  // Do not monitor storage for component extensions.
75  return extension->permissions_data()->HasAPIPermission(
76             APIPermission::kUnlimitedStorage) &&
77         extension->location() != Manifest::COMPONENT;
78}
79
80const Extension* GetExtensionById(content::BrowserContext* context,
81                                  const std::string& extension_id) {
82  return ExtensionRegistry::Get(context)->GetExtensionById(
83      extension_id, ExtensionRegistry::EVERYTHING);
84}
85
86}  // namespace
87
88// StorageEventObserver monitors the storage usage of extensions and lives on
89// the IO thread. When a threshold is exceeded, a message will be posted to the
90// UI thread, which displays the notification.
91class StorageEventObserver
92    : public base::RefCountedThreadSafe<StorageEventObserver,
93                                        BrowserThread::DeleteOnIOThread>,
94      public storage::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<storage::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    storage::StorageObserver::MonitorParams params(
118        kMonitorStorageType, origin, base::TimeDelta::FromSeconds(rate), false);
119    quota_manager->AddStorageObserver(this, params);
120  }
121
122  // Updates the threshold for an extension already being monitored.
123  void UpdateThresholdForExtension(const std::string& extension_id,
124                                   int64 next_threshold) {
125    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
126
127    for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
128         it != origin_state_map_.end();
129         ++it) {
130      if (it->second.extension_id == extension_id) {
131        it->second.next_threshold = next_threshold;
132        break;
133      }
134    }
135  }
136
137  // Deregister as an observer for the extension's storage events.
138  void StopObservingForExtension(const std::string& extension_id) {
139    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
140
141    for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
142         it != origin_state_map_.end(); ) {
143      if (it->second.extension_id == extension_id) {
144        storage::StorageObserver::Filter filter(kMonitorStorageType, it->first);
145        it->second.quota_manager->RemoveStorageObserverForFilter(this, filter);
146        origin_state_map_.erase(it++);
147      } else {
148        ++it;
149      }
150    }
151  }
152
153  // Stop observing all storage events. Called during shutdown.
154  void StopObserving() {
155    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
156
157    for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
158         it != origin_state_map_.end(); ++it) {
159      it->second.quota_manager->RemoveStorageObserver(this);
160    }
161    origin_state_map_.clear();
162  }
163
164 private:
165  friend class base::DeleteHelper<StorageEventObserver>;
166  friend struct content::BrowserThread::DeleteOnThread<
167      content::BrowserThread::IO>;
168
169  struct StorageState {
170    scoped_refptr<storage::QuotaManager> quota_manager;
171    std::string extension_id;
172    int64 next_threshold;
173
174    StorageState() : next_threshold(0) {}
175  };
176  typedef std::map<GURL, StorageState> OriginStorageStateMap;
177
178  virtual ~StorageEventObserver() {
179    DCHECK(origin_state_map_.empty());
180    StopObserving();
181  }
182
183  // storage::StorageObserver implementation.
184  virtual void OnStorageEvent(const Event& event) OVERRIDE {
185    OriginStorageStateMap::iterator state =
186        origin_state_map_.find(event.filter.origin);
187    if (state == origin_state_map_.end())
188      return;
189
190    if (event.usage >= state->second.next_threshold) {
191      while (event.usage >= state->second.next_threshold)
192        state->second.next_threshold *= 2;
193
194      BrowserThread::PostTask(
195          BrowserThread::UI,
196          FROM_HERE,
197          base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded,
198                     storage_monitor_,
199                     state->second.extension_id,
200                     state->second.next_threshold,
201                     event.usage));
202    }
203  }
204
205  OriginStorageStateMap origin_state_map_;
206  base::WeakPtr<ExtensionStorageMonitor> storage_monitor_;
207};
208
209// ExtensionStorageMonitor
210
211// static
212ExtensionStorageMonitor* ExtensionStorageMonitor::Get(
213    content::BrowserContext* context) {
214  return ExtensionStorageMonitorFactory::GetForBrowserContext(context);
215}
216
217ExtensionStorageMonitor::ExtensionStorageMonitor(
218    content::BrowserContext* context)
219    : enable_for_all_extensions_(false),
220      initial_extension_threshold_(kExtensionInitialThreshold),
221      initial_ephemeral_threshold_(kEphemeralAppInitialThreshold),
222      observer_rate_(kStorageEventRateSec),
223      context_(context),
224      extension_prefs_(ExtensionPrefs::Get(context)),
225      extension_registry_observer_(this),
226      weak_ptr_factory_(this) {
227  DCHECK(extension_prefs_);
228
229  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
230                 content::Source<content::BrowserContext>(context_));
231
232  extension_registry_observer_.Add(ExtensionRegistry::Get(context_));
233}
234
235ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
236
237void ExtensionStorageMonitor::Observe(
238    int type,
239    const content::NotificationSource& source,
240    const content::NotificationDetails& details) {
241  switch (type) {
242    case chrome::NOTIFICATION_PROFILE_DESTROYED: {
243      StopMonitoringAll();
244      break;
245    }
246    default:
247      NOTREACHED();
248  }
249}
250
251void ExtensionStorageMonitor::OnExtensionLoaded(
252    content::BrowserContext* browser_context,
253    const Extension* extension) {
254  StartMonitoringStorage(extension);
255}
256
257void ExtensionStorageMonitor::OnExtensionUnloaded(
258    content::BrowserContext* browser_context,
259    const Extension* extension,
260    UnloadedExtensionInfo::Reason reason) {
261  StopMonitoringStorage(extension->id());
262}
263
264void ExtensionStorageMonitor::OnExtensionWillBeInstalled(
265    content::BrowserContext* browser_context,
266    const Extension* extension,
267    bool is_update,
268    bool from_ephemeral,
269    const std::string& old_name) {
270  // If an ephemeral app was promoted to a regular installed app, we may need to
271  // increase its next threshold.
272  if (!from_ephemeral || !ShouldMonitorStorageFor(extension))
273    return;
274
275  if (!enable_for_all_extensions_) {
276    // If monitoring is not enabled for installed extensions, just stop
277    // monitoring.
278    SetNextStorageThreshold(extension->id(), 0);
279    StopMonitoringStorage(extension->id());
280    return;
281  }
282
283  int64 next_threshold = GetNextStorageThresholdFromPrefs(extension->id());
284  if (next_threshold <= initial_extension_threshold_) {
285    // Clear the next threshold in the prefs. This effectively raises it to
286    // |initial_extension_threshold_|. If the current threshold is already
287    // higher than this, leave it as is.
288    SetNextStorageThreshold(extension->id(), 0);
289
290    if (storage_observer_.get()) {
291      BrowserThread::PostTask(
292          BrowserThread::IO,
293          FROM_HERE,
294          base::Bind(&StorageEventObserver::UpdateThresholdForExtension,
295                     storage_observer_,
296                     extension->id(),
297                     initial_extension_threshold_));
298    }
299  }
300}
301
302void ExtensionStorageMonitor::OnExtensionUninstalled(
303    content::BrowserContext* browser_context,
304    const Extension* extension,
305    extensions::UninstallReason reason) {
306  RemoveNotificationForExtension(extension->id());
307}
308
309void ExtensionStorageMonitor::ExtensionUninstallAccepted() {
310  DCHECK(!uninstall_extension_id_.empty());
311
312  const Extension* extension = GetExtensionById(context_,
313                                                uninstall_extension_id_);
314  uninstall_extension_id_.clear();
315  if (!extension)
316    return;
317
318  ExtensionService* service =
319      ExtensionSystem::Get(context_)->extension_service();
320  DCHECK(service);
321  service->UninstallExtension(
322      extension->id(),
323      extensions::UNINSTALL_REASON_STORAGE_THRESHOLD_EXCEEDED,
324      base::Bind(&base::DoNothing),
325      NULL);
326}
327
328void ExtensionStorageMonitor::ExtensionUninstallCanceled() {
329  uninstall_extension_id_.clear();
330}
331
332std::string ExtensionStorageMonitor::GetNotificationId(
333    const std::string& extension_id) {
334  std::vector<std::string> placeholders;
335  placeholders.push_back(context_->GetPath().BaseName().MaybeAsASCII());
336  placeholders.push_back(extension_id);
337
338  return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL);
339}
340
341void ExtensionStorageMonitor::OnStorageThresholdExceeded(
342    const std::string& extension_id,
343    int64 next_threshold,
344    int64 current_usage) {
345  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
346
347  const Extension* extension = GetExtensionById(context_, extension_id);
348  if (!extension)
349    return;
350
351  if (GetNextStorageThreshold(extension->id()) < next_threshold)
352    SetNextStorageThreshold(extension->id(), next_threshold);
353
354  const int kIconSize = message_center::kNotificationIconSize;
355  ExtensionResource resource =  IconsInfo::GetIconResource(
356      extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
357  ImageLoader::Get(context_)->LoadImageAsync(
358      extension, resource, gfx::Size(kIconSize, kIconSize),
359      base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
360                 weak_ptr_factory_.GetWeakPtr(),
361                 extension_id,
362                 current_usage));
363}
364
365void ExtensionStorageMonitor::OnImageLoaded(
366    const std::string& extension_id,
367    int64 current_usage,
368    const gfx::Image& image) {
369  const Extension* extension = GetExtensionById(context_, extension_id);
370  if (!extension)
371    return;
372
373  // Remove any existing notifications to force a new notification to pop up.
374  std::string notification_id(GetNotificationId(extension_id));
375  message_center::MessageCenter::Get()->RemoveNotification(
376      notification_id, false);
377
378  message_center::RichNotificationData notification_data;
379  notification_data.buttons.push_back(message_center::ButtonInfo(
380      l10n_util::GetStringUTF16(extension->is_app() ?
381          IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
382          IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
383  notification_data.buttons.push_back(message_center::ButtonInfo(
384      l10n_util::GetStringUTF16(extension->is_app() ?
385          IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_APP :
386          IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_EXTENSION)));
387
388  gfx::Image notification_image(image);
389  if (notification_image.IsEmpty()) {
390    notification_image =
391        extension->is_app() ? gfx::Image(util::GetDefaultAppIcon())
392                            : gfx::Image(util::GetDefaultExtensionIcon());
393  }
394
395  scoped_ptr<message_center::Notification> notification;
396  notification.reset(new message_center::Notification(
397      message_center::NOTIFICATION_TYPE_SIMPLE,
398      notification_id,
399      l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
400      l10n_util::GetStringFUTF16(
401          IDS_EXTENSION_STORAGE_MONITOR_TEXT,
402          base::UTF8ToUTF16(extension->name()),
403          base::IntToString16(current_usage / kMBytes)),
404      notification_image,
405      base::string16() /* display source */,
406      message_center::NotifierId(
407          message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
408      notification_data,
409      new message_center::HandleNotificationButtonClickDelegate(base::Bind(
410          &ExtensionStorageMonitor::OnNotificationButtonClick,
411          weak_ptr_factory_.GetWeakPtr(),
412          extension_id))));
413  notification->SetSystemPriority();
414  message_center::MessageCenter::Get()->AddNotification(notification.Pass());
415
416  notified_extension_ids_.insert(extension_id);
417}
418
419void ExtensionStorageMonitor::OnNotificationButtonClick(
420    const std::string& extension_id, int button_index) {
421  switch (button_index) {
422    case BUTTON_DISABLE_NOTIFICATION: {
423      DisableStorageMonitoring(extension_id);
424      break;
425    }
426    case BUTTON_UNINSTALL: {
427      ShowUninstallPrompt(extension_id);
428      break;
429    }
430    default:
431      NOTREACHED();
432  }
433}
434
435void ExtensionStorageMonitor::DisableStorageMonitoring(
436    const std::string& extension_id) {
437  StopMonitoringStorage(extension_id);
438
439  SetStorageNotificationEnabled(extension_id, false);
440
441  message_center::MessageCenter::Get()->RemoveNotification(
442      GetNotificationId(extension_id), false);
443}
444
445void ExtensionStorageMonitor::StartMonitoringStorage(
446    const Extension* extension) {
447  if (!ShouldMonitorStorageFor(extension))
448    return;
449
450  // First apply this feature only to experimental ephemeral apps. If it works
451  // well, roll it out to all extensions and apps.
452  if (!enable_for_all_extensions_ &&
453      !extension_prefs_->IsEphemeralApp(extension->id())) {
454    return;
455  }
456
457  if (!IsStorageNotificationEnabled(extension->id()))
458    return;
459
460  // Lazily create the storage monitor proxy on the IO thread.
461  if (!storage_observer_.get()) {
462    storage_observer_ =
463        new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
464  }
465
466  GURL site_url =
467      extensions::util::GetSiteForExtensionId(extension->id(), context_);
468  content::StoragePartition* storage_partition =
469      content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
470  DCHECK(storage_partition);
471  scoped_refptr<storage::QuotaManager> quota_manager(
472      storage_partition->GetQuotaManager());
473
474  GURL storage_origin(site_url.GetOrigin());
475  if (extension->is_hosted_app())
476    storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
477
478  BrowserThread::PostTask(
479      BrowserThread::IO,
480      FROM_HERE,
481      base::Bind(&StorageEventObserver::StartObservingForExtension,
482                 storage_observer_,
483                 quota_manager,
484                 extension->id(),
485                 storage_origin,
486                 GetNextStorageThreshold(extension->id()),
487                 observer_rate_));
488}
489
490void ExtensionStorageMonitor::StopMonitoringStorage(
491    const std::string& extension_id) {
492  if (!storage_observer_.get())
493    return;
494
495  BrowserThread::PostTask(
496      BrowserThread::IO,
497      FROM_HERE,
498      base::Bind(&StorageEventObserver::StopObservingForExtension,
499                 storage_observer_,
500                 extension_id));
501}
502
503void ExtensionStorageMonitor::StopMonitoringAll() {
504  extension_registry_observer_.RemoveAll();
505
506  RemoveAllNotifications();
507
508  if (!storage_observer_.get())
509    return;
510
511  BrowserThread::PostTask(
512      BrowserThread::IO,
513      FROM_HERE,
514      base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
515  storage_observer_ = NULL;
516}
517
518void ExtensionStorageMonitor::RemoveNotificationForExtension(
519    const std::string& extension_id) {
520  std::set<std::string>::iterator ext_id =
521      notified_extension_ids_.find(extension_id);
522  if (ext_id == notified_extension_ids_.end())
523    return;
524
525  notified_extension_ids_.erase(ext_id);
526  message_center::MessageCenter::Get()->RemoveNotification(
527      GetNotificationId(extension_id), false);
528}
529
530void ExtensionStorageMonitor::RemoveAllNotifications() {
531  if (notified_extension_ids_.empty())
532    return;
533
534  message_center::MessageCenter* center = message_center::MessageCenter::Get();
535  DCHECK(center);
536  for (std::set<std::string>::iterator it = notified_extension_ids_.begin();
537       it != notified_extension_ids_.end(); ++it) {
538    center->RemoveNotification(GetNotificationId(*it), false);
539  }
540  notified_extension_ids_.clear();
541}
542
543void ExtensionStorageMonitor::ShowUninstallPrompt(
544    const std::string& extension_id) {
545  const Extension* extension = GetExtensionById(context_, extension_id);
546  if (!extension)
547    return;
548
549  if (!uninstall_dialog_.get()) {
550    uninstall_dialog_.reset(ExtensionUninstallDialog::Create(
551        Profile::FromBrowserContext(context_), NULL, this));
552  }
553
554  uninstall_extension_id_ = extension->id();
555  uninstall_dialog_->ConfirmUninstall(extension);
556}
557
558int64 ExtensionStorageMonitor::GetNextStorageThreshold(
559    const std::string& extension_id) const {
560  int next_threshold = GetNextStorageThresholdFromPrefs(extension_id);
561  if (next_threshold == 0) {
562    // The next threshold is written to the prefs after the initial threshold is
563    // exceeded.
564    next_threshold = extension_prefs_->IsEphemeralApp(extension_id)
565                         ? initial_ephemeral_threshold_
566                         : initial_extension_threshold_;
567  }
568  return next_threshold;
569}
570
571void ExtensionStorageMonitor::SetNextStorageThreshold(
572    const std::string& extension_id,
573    int64 next_threshold) {
574  extension_prefs_->UpdateExtensionPref(
575      extension_id,
576      kPrefNextStorageThreshold,
577      next_threshold > 0
578          ? new base::StringValue(base::Int64ToString(next_threshold))
579          : NULL);
580}
581
582int64 ExtensionStorageMonitor::GetNextStorageThresholdFromPrefs(
583    const std::string& extension_id) const {
584  std::string next_threshold_str;
585  if (extension_prefs_->ReadPrefAsString(
586          extension_id, kPrefNextStorageThreshold, &next_threshold_str)) {
587    int64 next_threshold;
588    if (base::StringToInt64(next_threshold_str, &next_threshold))
589      return next_threshold;
590  }
591
592  // A return value of zero indicates that the initial threshold has not yet
593  // been reached.
594  return 0;
595}
596
597bool ExtensionStorageMonitor::IsStorageNotificationEnabled(
598    const std::string& extension_id) const {
599  bool disable_notifications;
600  if (extension_prefs_->ReadPrefAsBoolean(extension_id,
601                                          kPrefDisableStorageNotifications,
602                                          &disable_notifications)) {
603    return !disable_notifications;
604  }
605
606  return true;
607}
608
609void ExtensionStorageMonitor::SetStorageNotificationEnabled(
610    const std::string& extension_id,
611    bool enable_notifications) {
612  extension_prefs_->UpdateExtensionPref(
613      extension_id,
614      kPrefDisableStorageNotifications,
615      enable_notifications ? NULL : new base::FundamentalValue(true));
616}
617
618}  // namespace extensions
619