Upstream version 7.36.149.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_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"
33
34 using content::BrowserThread;
35
36 namespace extensions {
37
38 namespace {
39
40 // The rate at which we would like to observe storage events.
41 const int kStorageEventRateSec = 30;
42
43 // The storage type to monitor.
44 const quota::StorageType kMonitorStorageType = quota::kStorageTypePersistent;
45
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;
52
53 // Notifications have an ID so that we can update them.
54 const char kNotificationIdFormat[] = "ExtensionStorageMonitor-$1-$2";
55 const char kSystemNotifierId[] = "ExtensionStorageMonitor";
56
57 }  // namespace
58
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<
64           StorageEventObserver,
65           BrowserThread::DeleteOnIOThread>,
66       public quota::StorageObserver {
67  public:
68   explicit StorageEventObserver(
69       base::WeakPtr<ExtensionStorageMonitor> storage_monitor)
70       : storage_monitor_(storage_monitor) {
71   }
72
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,
77       const GURL& site_url,
78       int64 next_threshold,
79       int rate) {
80     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
81     DCHECK(quota_manager.get());
82
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;
88
89     quota::StorageObserver::MonitorParams params(
90         kMonitorStorageType,
91         origin,
92         base::TimeDelta::FromSeconds(rate),
93         false);
94     quota_manager->AddStorageObserver(this, params);
95   }
96
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));
100
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++);
107       } else {
108         ++it;
109       }
110     }
111   }
112
113   // Stop observing all storage events. Called during shutdown.
114   void StopObserving() {
115     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
116
117     for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
118          it != origin_state_map_.end(); ++it) {
119       it->second.quota_manager->RemoveStorageObserver(this);
120     }
121     origin_state_map_.clear();
122   }
123
124  private:
125   friend class base::DeleteHelper<StorageEventObserver>;
126   friend struct content::BrowserThread::DeleteOnThread<
127       content::BrowserThread::IO>;
128
129   struct StorageState {
130     scoped_refptr<quota::QuotaManager> quota_manager;
131     std::string extension_id;
132     int64 next_threshold;
133
134     StorageState() : next_threshold(0) {}
135   };
136   typedef std::map<GURL, StorageState> OriginStorageStateMap;
137
138   virtual ~StorageEventObserver() {
139     DCHECK(origin_state_map_.empty());
140     StopObserving();
141   }
142
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())
148       return;
149
150     if (event.usage >= state->second.next_threshold) {
151       while (event.usage >= state->second.next_threshold)
152         state->second.next_threshold *= 2;
153
154       BrowserThread::PostTask(
155           BrowserThread::UI,
156           FROM_HERE,
157           base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded,
158                      storage_monitor_,
159                      state->second.extension_id,
160                      state->second.next_threshold,
161                      event.usage));
162     }
163   }
164
165   OriginStorageStateMap origin_state_map_;
166   base::WeakPtr<ExtensionStorageMonitor> storage_monitor_;
167 };
168
169 // ExtensionStorageMonitor
170
171 // static
172 ExtensionStorageMonitor* ExtensionStorageMonitor::Get(
173     content::BrowserContext* context) {
174   return ExtensionStorageMonitorFactory::GetForBrowserContext(context);
175 }
176
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),
183       context_(context),
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_));
189
190   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
191   DCHECK(registry);
192   registry->AddObserver(this);
193 }
194
195 ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
196
197 void ExtensionStorageMonitor::Observe(
198     int type,
199     const content::NotificationSource& source,
200     const content::NotificationDetails& details) {
201   switch (type) {
202     case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
203       const Extension* extension =
204           content::Details<const Extension>(details).ptr();
205       RemoveNotificationForExtension(extension->id());
206       break;
207     }
208     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
209       StopMonitoringAll();
210       break;
211     }
212     default:
213       NOTREACHED();
214   };
215 }
216
217 void ExtensionStorageMonitor::OnExtensionLoaded(
218     content::BrowserContext* browser_context,
219     const Extension* extension) {
220   DCHECK(extension);
221   StartMonitoringStorage(extension);
222 }
223
224 void ExtensionStorageMonitor::OnExtensionUnloaded(
225     content::BrowserContext* browser_context,
226     const Extension* extension,
227     UnloadedExtensionInfo::Reason reason) {
228   DCHECK(extension);
229   StopMonitoringStorage(extension->id());
230 }
231
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);
237
238   return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL);
239 }
240
241 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
242     const std::string& extension_id,
243     int64 next_threshold,
244     int64 current_usage) {
245   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
246
247   const Extension* extension = ExtensionRegistry::Get(context_)->
248       GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
249   if (!extension)
250     return;
251
252   ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
253   DCHECK(prefs);
254   prefs->SetNextStorageThreshold(extension->id(), next_threshold);
255
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(),
263                  extension_id,
264                  current_usage));
265 }
266
267 void ExtensionStorageMonitor::OnImageLoaded(
268     const std::string& extension_id,
269     int64 current_usage,
270     const gfx::Image& image) {
271   const Extension* extension = ExtensionRegistry::Get(context_)->
272       GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
273   if (!extension)
274     return;
275
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);
280
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)));
286
287   gfx::Image notification_image(image);
288   if (notification_image.IsEmpty()) {
289     notification_image =
290         extension->is_app() ? gfx::Image(util::GetDefaultAppIcon())
291                             : gfx::Image(util::GetDefaultExtensionIcon());
292   }
293
294   scoped_ptr<message_center::Notification> notification;
295   notification.reset(new message_center::Notification(
296       message_center::NOTIFICATION_TYPE_SIMPLE,
297       notification_id,
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)),
303       notification_image,
304       base::string16() /* display source */,
305       message_center::NotifierId(
306           message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
307       notification_data,
308       new message_center::HandleNotificationButtonClickDelegate(base::Bind(
309           &ExtensionStorageMonitor::OnNotificationButtonClick,
310           weak_ptr_factory_.GetWeakPtr(),
311           extension_id))));
312   notification->SetSystemPriority();
313   message_center::MessageCenter::Get()->AddNotification(notification.Pass());
314
315   notified_extension_ids_.insert(extension_id);
316 }
317
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);
323       break;
324     }
325     default:
326       NOTREACHED();
327   };
328 }
329
330 void ExtensionStorageMonitor::DisableStorageMonitoring(
331     const std::string& extension_id) {
332   StopMonitoringStorage(extension_id);
333
334   ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
335   DCHECK(prefs);
336   prefs->SetStorageNotificationEnabled(extension_id, false);
337
338   message_center::MessageCenter::Get()->RemoveNotification(
339       GetNotificationId(extension_id), false);
340 }
341
342 void ExtensionStorageMonitor::StartMonitoringStorage(
343     const Extension* extension) {
344   if (!extension->HasAPIPermission(APIPermission::kUnlimitedStorage))
345     return;
346
347   // Do not monitor storage for component extensions.
348   if (extension->location() == Manifest::COMPONENT)
349     return;
350
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_)
354     return;
355
356   ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
357   DCHECK(prefs);
358   if (!prefs->IsStorageNotificationEnabled(extension->id()))
359     return;
360
361   // Lazily create the storage monitor proxy on the IO thread.
362   if (!storage_observer_.get()) {
363     storage_observer_ =
364         new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
365   }
366
367   GURL site_url =
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());
374
375   GURL storage_origin(site_url.GetOrigin());
376   if (extension->is_hosted_app())
377     storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
378
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
382     // exceeded.
383     next_threshold = extension->is_ephemeral() ? initial_ephemeral_threshold_
384                                                : initial_extension_threshold_;
385   }
386
387   BrowserThread::PostTask(
388       BrowserThread::IO,
389       FROM_HERE,
390       base::Bind(&StorageEventObserver::StartObservingForExtension,
391                  storage_observer_,
392                  quota_manager,
393                  extension->id(),
394                  storage_origin,
395                  next_threshold,
396                  observer_rate_));
397 }
398
399 void ExtensionStorageMonitor::StopMonitoringStorage(
400     const std::string& extension_id) {
401   if (!storage_observer_.get())
402     return;
403
404   BrowserThread::PostTask(
405       BrowserThread::IO,
406       FROM_HERE,
407       base::Bind(&StorageEventObserver::StopObservingForExtension,
408                  storage_observer_,
409                  extension_id));
410 }
411
412 void ExtensionStorageMonitor::StopMonitoringAll() {
413   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
414   DCHECK(registry);
415   registry->RemoveObserver(this);
416
417   RemoveAllNotifications();
418
419   if (!storage_observer_.get())
420     return;
421
422   BrowserThread::PostTask(
423       BrowserThread::IO,
424       FROM_HERE,
425       base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
426   storage_observer_ = NULL;
427 }
428
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())
434     return;
435
436   notified_extension_ids_.erase(ext_id);
437   message_center::MessageCenter::Get()->RemoveNotification(
438       GetNotificationId(extension_id), false);
439 }
440
441 void ExtensionStorageMonitor::RemoveAllNotifications() {
442   if (notified_extension_ids_.empty())
443     return;
444
445   message_center::MessageCenter* center = message_center::MessageCenter::Get();
446   DCHECK(center);
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);
450   }
451   notified_extension_ids_.clear();
452 }
453
454 }  // namespace extensions