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

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 test failures 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/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 "chrome/common/extensions/manifest_handlers/icons_handler.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/common/extension.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 DCHECK(extension);
228 StopMonitoringStorage(extension->id());
229 }
230
231 std::string ExtensionStorageMonitor::GetNotificationId(
232 const std::string& extension_id) {
233 std::vector<std::string> placeholders;
234 placeholders.push_back(context_->GetPath().BaseName().MaybeAsASCII());
235 placeholders.push_back(extension_id);
236
237 return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL);
238 }
239
240 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
241 const std::string& extension_id,
242 int64 next_threshold,
243 int64 current_usage) {
244 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
245
246 const Extension* extension = ExtensionRegistry::Get(context_)->
247 GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
248 if (!extension)
249 return;
250
251 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
252 DCHECK(prefs);
253 prefs->SetNextStorageThreshold(extension->id(), next_threshold);
254
255 const int kIconSize = message_center::kNotificationIconSize;
256 ExtensionResource resource = IconsInfo::GetIconResource(
257 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
258 ImageLoader::Get(context_)->LoadImageAsync(
259 extension, resource, gfx::Size(kIconSize, kIconSize),
260 base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
261 weak_ptr_factory_.GetWeakPtr(),
262 extension_id,
263 current_usage));
264 }
265
266 void ExtensionStorageMonitor::OnImageLoaded(
267 const std::string& extension_id,
268 int64 current_usage,
269 const gfx::Image& image) {
270 const Extension* extension = ExtensionRegistry::Get(context_)->
271 GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
272 if (!extension)
273 return;
274
275 // Remove any existing notifications to force a new notification to pop up.
276 std::string notification_id(GetNotificationId(extension_id));
277 message_center::MessageCenter::Get()->RemoveNotification(
278 notification_id, false);
279
280 message_center::RichNotificationData notification_data;
281 notification_data.buttons.push_back(message_center::ButtonInfo(
282 l10n_util::GetStringUTF16(extension->is_app() ?
283 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
284 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
285
286 gfx::Image notification_image(image);
287 if (notification_image.IsEmpty()) {
288 notification_image =
289 extension->is_app() ? gfx::Image(IconsInfo::GetDefaultAppIcon())
290 : gfx::Image(IconsInfo::GetDefaultExtensionIcon());
291 }
292
293 scoped_ptr<message_center::Notification> notification;
294 notification.reset(new message_center::Notification(
295 message_center::NOTIFICATION_TYPE_SIMPLE,
296 notification_id,
297 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
298 l10n_util::GetStringFUTF16(
299 IDS_EXTENSION_STORAGE_MONITOR_TEXT,
300 base::UTF8ToUTF16(extension->name()),
301 base::IntToString16(current_usage / kMBytes)),
302 notification_image,
303 base::string16() /* display source */,
304 message_center::NotifierId(
305 message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
306 notification_data,
307 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
308 &ExtensionStorageMonitor::OnNotificationButtonClick,
309 weak_ptr_factory_.GetWeakPtr(),
310 extension_id))));
311 notification->SetSystemPriority();
312 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
313
314 notified_extension_ids_.insert(extension_id);
315 }
316
317 void ExtensionStorageMonitor::OnNotificationButtonClick(
318 const std::string& extension_id, int button_index) {
319 switch (button_index) {
320 case BUTTON_DISABLE_NOTIFICATION: {
321 DisableStorageMonitoring(extension_id);
322 break;
323 }
324 default:
325 NOTREACHED();
326 };
327 }
328
329 void ExtensionStorageMonitor::DisableStorageMonitoring(
330 const std::string& extension_id) {
331 StopMonitoringStorage(extension_id);
332
333 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
334 DCHECK(prefs);
335 prefs->SetStorageNotificationEnabled(extension_id, false);
336
337 message_center::MessageCenter::Get()->RemoveNotification(
338 GetNotificationId(extension_id), false);
339 }
340
341 void ExtensionStorageMonitor::StartMonitoringStorage(
342 const Extension* extension) {
343 if (!extension->HasAPIPermission(APIPermission::kUnlimitedStorage))
344 return;
345
346 // Do not monitor storage for component extensions.
347 if (extension->location() == Manifest::COMPONENT)
348 return;
349
350 // First apply this feature only to experimental ephemeral apps. If it works
351 // well, roll it out to all extensions and apps.
352 if (!extension->is_ephemeral() && !enable_for_all_extensions_)
353 return;
354
355 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
356 DCHECK(prefs);
357 if (!prefs->IsStorageNotificationEnabled(extension->id()))
358 return;
359
360 // Lazily create the storage monitor proxy on the IO thread.
361 if (!storage_observer_.get()) {
362 storage_observer_ =
363 new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
364 }
365
366 GURL site_url =
367 extensions::util::GetSiteForExtensionId(extension->id(), context_);
368 content::StoragePartition* storage_partition =
369 content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
370 DCHECK(storage_partition);
371 scoped_refptr<quota::QuotaManager> quota_manager(
372 storage_partition->GetQuotaManager());
373
374 GURL storage_origin(site_url.GetOrigin());
375 if (extension->is_hosted_app())
376 storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
377
378 int next_threshold = prefs->GetNextStorageThreshold(extension->id());
379 if (next_threshold == 0) {
380 // The next threshold is written to the prefs after the initial threshold is
381 // exceeded.
382 next_threshold = extension->is_ephemeral() ? initial_ephemeral_threshold_
383 : initial_extension_threshold_;
384 }
385
386 BrowserThread::PostTask(
387 BrowserThread::IO,
388 FROM_HERE,
389 base::Bind(&StorageEventObserver::StartObservingForExtension,
390 storage_observer_,
391 quota_manager,
392 extension->id(),
393 storage_origin,
394 next_threshold,
395 observer_rate_));
396 }
397
398 void ExtensionStorageMonitor::StopMonitoringStorage(
399 const std::string& extension_id) {
400 if (!storage_observer_.get())
401 return;
402
403 BrowserThread::PostTask(
404 BrowserThread::IO,
405 FROM_HERE,
406 base::Bind(&StorageEventObserver::StopObservingForExtension,
407 storage_observer_,
408 extension_id));
409 }
410
411 void ExtensionStorageMonitor::StopMonitoringAll() {
412 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
413 DCHECK(registry);
414 registry->RemoveObserver(this);
415
416 RemoveAllNotifications();
417
418 if (!storage_observer_.get())
419 return;
420
421 BrowserThread::PostTask(
422 BrowserThread::IO,
423 FROM_HERE,
424 base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
425 storage_observer_ = NULL;
426 }
427
428 void ExtensionStorageMonitor::RemoveNotificationForExtension(
429 const std::string& extension_id) {
430 std::set<std::string>::iterator ext_id =
431 notified_extension_ids_.find(extension_id);
432 if (ext_id == notified_extension_ids_.end())
433 return;
434
435 notified_extension_ids_.erase(ext_id);
436 message_center::MessageCenter::Get()->RemoveNotification(
437 GetNotificationId(extension_id), false);
438 }
439
440 void ExtensionStorageMonitor::RemoveAllNotifications() {
441 if (notified_extension_ids_.empty())
442 return;
443
444 message_center::MessageCenter* center = message_center::MessageCenter::Get();
445 DCHECK(center);
446 for (std::set<std::string>::iterator it = notified_extension_ids_.begin();
447 it != notified_extension_ids_.end(); ++it) {
448 center->RemoveNotification(GetNotificationId(*it), false);
449 }
450 notified_extension_ids_.clear();
451 }
452
453 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698