Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(40)

Side by Side Diff: chrome/browser/extensions/extension_storage_monitor.cc

Issue 221933013: Show a notification when an ephemeral app consumes excessive disk space (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@webkit_storage_monitor
Patch Set: Fix unit tests Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/utf_string_conversions.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/extensions/extension_storage_monitor_factory.h"
13 #include "chrome/browser/extensions/extension_util.h"
14 #include "chrome/browser/extensions/image_loader.h"
15 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
16 #include "chrome/common/extensions/manifest_handlers/icons_handler.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/extension_resource.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 "webkit/browser/quota/quota_manager.h"
31 #include "webkit/browser/quota/storage_observer.h"
32
33 using content::BrowserThread;
34
35 namespace extensions {
36
37 namespace {
38
39 // The rate at which we would like to observe storage events.
40 const int kStorageEventRateSec = 30;
41
42 // The storage type to monitor.
43 const quota::StorageType kMonitorStorageType = quota::kStorageTypePersistent;
44
45 // Set the thresholds for the first notification. Ephemeral apps have a lower
46 // threshold than installed extensions and apps. Once a threshold is exceeded,
47 // it will be doubled to throttle notifications.
48 const int64 kMBytes = 1024 * 1024;
49 const int64 kEphemeralAppInitialThreshold = 250 * kMBytes;
50 const int64 kExtensionInitialThreshold = 1000 * kMBytes;
51
52 // Notifications have an ID so that we can update them.
53 const char kNotificationIdPrefix[] = "ExtensionStorageMonitor_";
54 const char kSystemNotifierId[] = "ExtensionStorageMonitor";
55
56 } // namespace
57
58 // StorageEventObserver monitors the storage usage of extensions and lives on
59 // the IO thread. When a threshold is exceeded, a message will be posted to the
60 // UI thread, which displays the notification.
61 class StorageEventObserver
62 : public base::RefCountedThreadSafe<
63 StorageEventObserver,
64 BrowserThread::DeleteOnIOThread>,
65 public quota::StorageObserver {
66 public:
67 explicit StorageEventObserver(
68 base::WeakPtr<ExtensionStorageMonitor> storage_monitor)
69 : storage_monitor_(storage_monitor) {
70 }
71
72 // Register as an observer for the extension's storage events.
73 void StartObservingForExtension(
74 scoped_refptr<quota::QuotaManager> quota_manager,
75 const std::string& extension_id,
76 const GURL& site_url,
77 int64 next_threshold,
78 int rate) {
79 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
80 DCHECK(quota_manager.get());
81
82 GURL origin = site_url.GetOrigin();
83 StorageState& state = origin_state_map_[origin];
84 state.quota_manager = quota_manager;
85 state.extension_id = extension_id;
86 state.next_threshold = next_threshold;
87
88 quota::StorageObserver::MonitorParams params(
89 kMonitorStorageType,
90 origin,
91 base::TimeDelta::FromSeconds(rate),
92 false);
93 quota_manager->AddStorageObserver(this, params);
94 }
95
96 // Deregister as an observer for the extension's storage events.
97 void StopObservingForExtension(const std::string& extension_id) {
98 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
99
100 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
101 it != origin_state_map_.end(); ) {
102 if (it->second.extension_id == extension_id) {
103 quota::StorageObserver::Filter filter(kMonitorStorageType, it->first);
104 it->second.quota_manager->RemoveStorageObserverForFilter(this, filter);
105 origin_state_map_.erase(it++);
koz (OOO until 15th September) 2014/04/07 05:24:32 Can it++ not go in the for-part?
tmdiep 2014/04/07 08:45:01 I need the return value of the post-increment oper
koz (OOO until 15th September) 2014/04/08 00:26:00 Ah, I see. Very cool :-)
106 } else {
107 ++it;
108 }
109 }
110 }
111
112 // Stop observing all storage events. Called during shutdown.
113 void StopObserving() {
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
115
116 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
117 it != origin_state_map_.end(); ++it) {
118 it->second.quota_manager->RemoveStorageObserver(this);
119 }
120 origin_state_map_.clear();
121 }
122
123 private:
124 friend class base::DeleteHelper<StorageEventObserver>;
125 friend struct content::BrowserThread::DeleteOnThread<
126 content::BrowserThread::IO>;
127
128 struct StorageState {
129 scoped_refptr<quota::QuotaManager> quota_manager;
130 std::string extension_id;
131 int64 next_threshold;
132
133 StorageState() : next_threshold(0) {}
134 };
135 typedef std::map<GURL, StorageState> OriginStorageStateMap;
136
137 virtual ~StorageEventObserver() {
138 // The observers should have been cleared by now, but double-check before
139 // destruction.
koz (OOO until 15th September) 2014/04/07 05:24:32 nit: Replace comment with DCHECK(origin_state_map_
tmdiep 2014/04/07 08:45:01 Done.
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_LOADED,
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_LOADED: {
203 const Extension* extension =
204 content::Details<const Extension>(details).ptr();
205 StartMonitoringStorage(extension);
206 break;
207 }
208 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
209 StopMonitoringAll();
koz (OOO until 15th September) 2014/04/07 05:24:32 Should this also clear any notifications that are
tmdiep 2014/04/07 08:45:01 Hmm, yes clearing the notifications would be a goo
210 break;
211 }
212 default:
213 NOTREACHED();
214 };
215 }
216
217 void ExtensionStorageMonitor::OnExtensionUnloaded(const Extension* extension) {
218 DCHECK(extension);
219 StopMonitoringStorage(extension->id());
koz (OOO until 15th September) 2014/04/07 05:24:32 Same as above: when an extension gets unloaded, sh
tmdiep 2014/04/07 08:45:01 I removed the notification on uninstall. Unloading
220 }
221
222 // static
223 std::string ExtensionStorageMonitor::GetNotificationId(
224 const std::string& extension_id) {
225 return kNotificationIdPrefix + extension_id;
226 }
227
228 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
229 const std::string& extension_id,
230 int64 next_threshold,
231 int64 current_usage) {
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
233
234 const Extension* extension = ExtensionRegistry::Get(context_)->
235 GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
236 if (!extension)
237 return;
238
239 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
240 DCHECK(prefs);
241 prefs->SetNextStorageThreshold(extension->id(), next_threshold);
242
243 const int kIconSize = extension_misc::EXTENSION_ICON_LARGE;
244 ExtensionResource resource = IconsInfo::GetIconResource(
245 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
246 ImageLoader::Get(context_)->LoadImageAsync(
247 extension, resource, gfx::Size(kIconSize, kIconSize),
248 base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
249 weak_ptr_factory_.GetWeakPtr(),
250 extension_id,
251 current_usage));
252 }
253
254 void ExtensionStorageMonitor::OnImageLoaded(
255 const std::string& extension_id,
256 int64 current_usage,
257 const gfx::Image& image) {
258 const Extension* extension = ExtensionRegistry::Get(context_)->
259 GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
260 if (!extension)
261 return;
262
263 // Remove any existing notifications to force a new notification to pop up.
264 std::string notification_id(GetNotificationId(extension_id));
265 message_center::MessageCenter::Get()->RemoveNotification(
266 notification_id, false);
267
268 message_center::RichNotificationData notification_data;
269 notification_data.buttons.push_back(message_center::ButtonInfo(
270 l10n_util::GetStringUTF16(extension->is_app() ?
271 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
272 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
273
274 scoped_ptr<message_center::Notification> notification;
275 notification.reset(new message_center::Notification(
276 message_center::NOTIFICATION_TYPE_SIMPLE,
277 notification_id,
278 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
279 l10n_util::GetStringFUTF16(
280 IDS_EXTENSION_STORAGE_MONITOR_TEXT,
281 base::UTF8ToUTF16(extension->name()),
282 base::IntToString16(current_usage / kMBytes)),
283 image,
284 base::string16() /* display source */,
285 message_center::NotifierId(
286 message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
287 notification_data,
288 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
289 &ExtensionStorageMonitor::OnNotificationButtonClick,
290 weak_ptr_factory_.GetWeakPtr(),
291 extension_id))));
292 notification->SetSystemPriority();
293 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
294 }
295
296 void ExtensionStorageMonitor::OnNotificationButtonClick(
297 const std::string& extension_id, int button_index) {
298 switch (button_index) {
299 case BUTTON_DISABLE_NOTIFICATION: {
300 DisableStorageMonitoring(extension_id);
301 break;
302 }
303 default:
304 NOTREACHED();
305 };
306 }
307
308 void ExtensionStorageMonitor::DisableStorageMonitoring(
309 const std::string& extension_id) {
310 StopMonitoringStorage(extension_id);
311
312 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
313 DCHECK(prefs);
314 prefs->SetStorageNotificationsEnabled(extension_id, false);
315
316 message_center::MessageCenter::Get()->RemoveNotification(
317 GetNotificationId(extension_id), false);
318 }
319
320 void ExtensionStorageMonitor::StartMonitoringStorage(
321 const Extension* extension) {
322 if (!extension->HasAPIPermission(APIPermission::kUnlimitedStorage))
323 return;
324
325 // Do not monitor storage for component extensions.
326 if (extension->location() == Manifest::COMPONENT)
327 return;
328
329 // First apply this feature only to experimental ephemeral apps. If it works
330 // well, roll it out to all extensions and apps.
331 if (!extension->is_ephemeral() && !enable_for_all_extensions_)
332 return;
333
334 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
335 DCHECK(prefs);
336 if (!prefs->StorageNotificationsEnabled(extension->id()))
337 return;
338
339 // Lazily create the storage monitor proxy on the IO thread.
340 if (!storage_observer_.get()) {
341 storage_observer_ =
342 new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
343 }
344
345 GURL site_url =
346 extensions::util::GetSiteForExtensionId(extension->id(), context_);
347 content::StoragePartition* storage_partition =
348 content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
349 DCHECK(storage_partition);
350 scoped_refptr<quota::QuotaManager> quota_manager(
351 storage_partition->GetQuotaManager());
352
353 GURL storage_origin(site_url.GetOrigin());
354 if (extension->is_hosted_app())
355 storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
356
357 int next_threshold = prefs->GetNextStorageThreshold(extension->id());
358 if (next_threshold == 0) {
359 // The next threshold is written to the prefs after the initial threshold is
360 // exceeded.
361 next_threshold = extension->is_ephemeral() ? initial_ephemeral_threshold_
362 : initial_extension_threshold_;
363 }
364
365 BrowserThread::PostTask(
366 BrowserThread::IO,
367 FROM_HERE,
368 base::Bind(&StorageEventObserver::StartObservingForExtension,
369 storage_observer_,
370 quota_manager,
371 extension->id(),
372 storage_origin,
373 next_threshold,
374 observer_rate_));
375 }
376
377 void ExtensionStorageMonitor::StopMonitoringStorage(
378 const std::string& extension_id) {
379 if (!storage_observer_.get())
380 return;
381
382 BrowserThread::PostTask(
383 BrowserThread::IO,
384 FROM_HERE,
385 base::Bind(&StorageEventObserver::StopObservingForExtension,
386 storage_observer_,
387 extension_id));
388 }
389
390 void ExtensionStorageMonitor::StopMonitoringAll() {
391 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
392 DCHECK(registry);
393 registry->RemoveObserver(this);
394
395 if (!storage_observer_.get())
396 return;
397
398 BrowserThread::PostTask(
399 BrowserThread::IO,
400 FROM_HERE,
401 base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
402
403 storage_observer_ = NULL;
404 }
405
406 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698