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

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: Addressed review comments 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++);
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 DCHECK(origin_state_map_.empty());
139 StopObserving();
140 }
141
142 // quota::StorageObserver implementation.
143 virtual void OnStorageEvent(const Event& event) OVERRIDE {
144 OriginStorageStateMap::iterator state =
145 origin_state_map_.find(event.filter.origin);
146 if (state == origin_state_map_.end())
147 return;
148
149 if (event.usage >= state->second.next_threshold) {
150 while (event.usage >= state->second.next_threshold)
151 state->second.next_threshold *= 2;
152
153 BrowserThread::PostTask(
154 BrowserThread::UI,
155 FROM_HERE,
156 base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded,
157 storage_monitor_,
158 state->second.extension_id,
159 state->second.next_threshold,
160 event.usage));
161 }
162 }
163
164 OriginStorageStateMap origin_state_map_;
165 base::WeakPtr<ExtensionStorageMonitor> storage_monitor_;
166 };
167
168 // ExtensionStorageMonitor
169
170 // static
171 ExtensionStorageMonitor* ExtensionStorageMonitor::Get(
172 content::BrowserContext* context) {
173 return ExtensionStorageMonitorFactory::GetForBrowserContext(context);
174 }
175
176 ExtensionStorageMonitor::ExtensionStorageMonitor(
177 content::BrowserContext* context)
178 : enable_for_all_extensions_(false),
179 initial_extension_threshold_(kExtensionInitialThreshold),
180 initial_ephemeral_threshold_(kEphemeralAppInitialThreshold),
181 observer_rate_(kStorageEventRateSec),
182 context_(context),
183 weak_ptr_factory_(this) {
184 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
185 content::Source<content::BrowserContext>(context_));
186 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
187 content::Source<content::BrowserContext>(context_));
188 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
189 content::Source<content::BrowserContext>(context_));
190
191 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
192 DCHECK(registry);
193 registry->AddObserver(this);
194 }
195
196 ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
197
198 void ExtensionStorageMonitor::Observe(
199 int type,
200 const content::NotificationSource& source,
201 const content::NotificationDetails& details) {
202 switch (type) {
203 case chrome::NOTIFICATION_EXTENSION_LOADED: {
204 const Extension* extension =
205 content::Details<const Extension>(details).ptr();
206 StartMonitoringStorage(extension);
207 break;
208 }
209 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
210 const Extension* extension =
211 content::Details<const Extension>(details).ptr();
212 RemoveNotificationForExtension(extension->id());
213 break;
214 }
215 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
216 StopMonitoringAll();
217 break;
218 }
219 default:
220 NOTREACHED();
221 };
222 }
223
224 void ExtensionStorageMonitor::OnExtensionUnloaded(const Extension* extension) {
225 DCHECK(extension);
226 StopMonitoringStorage(extension->id());
227 }
228
229 std::string ExtensionStorageMonitor::GetNotificationId(
230 const std::string& extension_id) {
231 return kNotificationIdPrefix + context_->GetPath().BaseName().MaybeAsASCII()
dewittj 2014/04/08 01:08:03 Can you please encode profile into the notificatio
tmdiep 2014/04/08 02:28:01 GetPath().BaseName() adds the profile name to the
232 + extension_id;
233 }
234
235 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
236 const std::string& extension_id,
237 int64 next_threshold,
238 int64 current_usage) {
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240
241 const Extension* extension = ExtensionRegistry::Get(context_)->
242 GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
243 if (!extension)
244 return;
245
246 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
247 DCHECK(prefs);
248 prefs->SetNextStorageThreshold(extension->id(), next_threshold);
249
250 const int kIconSize = extension_misc::EXTENSION_ICON_LARGE;
dewittj 2014/04/08 01:08:03 The icon size you need is message_center::kNotific
tmdiep 2014/04/08 02:28:01 Done.
251 ExtensionResource resource = IconsInfo::GetIconResource(
252 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
dewittj 2014/04/08 01:08:03 Does this handle the case where no resource matche
tmdiep 2014/04/08 02:28:01 Fixed - fallback to the default app or extension i
253 ImageLoader::Get(context_)->LoadImageAsync(
254 extension, resource, gfx::Size(kIconSize, kIconSize),
255 base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
256 weak_ptr_factory_.GetWeakPtr(),
257 extension_id,
258 current_usage));
259 }
260
261 void ExtensionStorageMonitor::OnImageLoaded(
262 const std::string& extension_id,
263 int64 current_usage,
264 const gfx::Image& image) {
265 const Extension* extension = ExtensionRegistry::Get(context_)->
266 GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
267 if (!extension)
268 return;
269
270 // Remove any existing notifications to force a new notification to pop up.
271 std::string notification_id(GetNotificationId(extension_id));
272 message_center::MessageCenter::Get()->RemoveNotification(
273 notification_id, false);
274
275 message_center::RichNotificationData notification_data;
276 notification_data.buttons.push_back(message_center::ButtonInfo(
277 l10n_util::GetStringUTF16(extension->is_app() ?
278 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
279 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
280
281 scoped_ptr<message_center::Notification> notification;
282 notification.reset(new message_center::Notification(
283 message_center::NOTIFICATION_TYPE_SIMPLE,
284 notification_id,
285 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
286 l10n_util::GetStringFUTF16(
287 IDS_EXTENSION_STORAGE_MONITOR_TEXT,
288 base::UTF8ToUTF16(extension->name()),
289 base::IntToString16(current_usage / kMBytes)),
290 image,
291 base::string16() /* display source */,
dewittj 2014/04/08 01:08:03 Ideally something would be in here. If you right-
tmdiep 2014/04/08 02:28:01 This menu item seems to have been hidden as a resu
dewittj 2014/04/08 16:52:29 Oh okay, cool, probably that way for system notifi
292 message_center::NotifierId(
293 message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
294 notification_data,
295 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
dewittj 2014/04/08 01:08:03 I think you should just use a specialized Notifica
tmdiep 2014/04/08 02:28:01 Yes, once a threshold is hit, the next threshold i
296 &ExtensionStorageMonitor::OnNotificationButtonClick,
297 weak_ptr_factory_.GetWeakPtr(),
298 extension_id))));
299 notification->SetSystemPriority();
dewittj 2014/04/08 01:08:03 You want this to be non-auto-hide? Is there a com
tmdiep 2014/04/08 02:28:01 This is considered to be a system level warning of
300 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
301
302 notified_extension_ids_.insert(extension_id);
303 }
304
305 void ExtensionStorageMonitor::OnNotificationButtonClick(
306 const std::string& extension_id, int button_index) {
307 switch (button_index) {
308 case BUTTON_DISABLE_NOTIFICATION: {
309 DisableStorageMonitoring(extension_id);
310 break;
311 }
312 default:
313 NOTREACHED();
314 };
315 }
316
317 void ExtensionStorageMonitor::DisableStorageMonitoring(
318 const std::string& extension_id) {
319 StopMonitoringStorage(extension_id);
320
321 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
322 DCHECK(prefs);
323 prefs->SetStorageNotificationEnabled(extension_id, false);
324
325 message_center::MessageCenter::Get()->RemoveNotification(
326 GetNotificationId(extension_id), false);
327 }
328
329 void ExtensionStorageMonitor::StartMonitoringStorage(
330 const Extension* extension) {
331 if (!extension->HasAPIPermission(APIPermission::kUnlimitedStorage))
332 return;
333
334 // Do not monitor storage for component extensions.
335 if (extension->location() == Manifest::COMPONENT)
336 return;
337
338 // First apply this feature only to experimental ephemeral apps. If it works
339 // well, roll it out to all extensions and apps.
340 if (!extension->is_ephemeral() && !enable_for_all_extensions_)
341 return;
342
343 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
344 DCHECK(prefs);
345 if (!prefs->IsStorageNotificationEnabled(extension->id()))
346 return;
347
348 // Lazily create the storage monitor proxy on the IO thread.
349 if (!storage_observer_.get()) {
350 storage_observer_ =
351 new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
352 }
353
354 GURL site_url =
355 extensions::util::GetSiteForExtensionId(extension->id(), context_);
356 content::StoragePartition* storage_partition =
357 content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
358 DCHECK(storage_partition);
359 scoped_refptr<quota::QuotaManager> quota_manager(
360 storage_partition->GetQuotaManager());
361
362 GURL storage_origin(site_url.GetOrigin());
363 if (extension->is_hosted_app())
364 storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
365
366 int next_threshold = prefs->GetNextStorageThreshold(extension->id());
367 if (next_threshold == 0) {
368 // The next threshold is written to the prefs after the initial threshold is
369 // exceeded.
370 next_threshold = extension->is_ephemeral() ? initial_ephemeral_threshold_
371 : initial_extension_threshold_;
372 }
373
374 BrowserThread::PostTask(
375 BrowserThread::IO,
376 FROM_HERE,
377 base::Bind(&StorageEventObserver::StartObservingForExtension,
378 storage_observer_,
379 quota_manager,
380 extension->id(),
381 storage_origin,
382 next_threshold,
383 observer_rate_));
384 }
385
386 void ExtensionStorageMonitor::StopMonitoringStorage(
387 const std::string& extension_id) {
388 if (!storage_observer_.get())
389 return;
390
391 BrowserThread::PostTask(
392 BrowserThread::IO,
393 FROM_HERE,
394 base::Bind(&StorageEventObserver::StopObservingForExtension,
395 storage_observer_,
396 extension_id));
397 }
398
399 void ExtensionStorageMonitor::StopMonitoringAll() {
400 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
401 DCHECK(registry);
402 registry->RemoveObserver(this);
403
404 RemoveAllNotifications();
405
406 if (!storage_observer_.get())
407 return;
408
409 BrowserThread::PostTask(
410 BrowserThread::IO,
411 FROM_HERE,
412 base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
413 storage_observer_ = NULL;
414 }
415
416 void ExtensionStorageMonitor::RemoveNotificationForExtension(
417 const std::string& extension_id) {
418 std::set<std::string>::iterator ext_id =
419 notified_extension_ids_.find(extension_id);
420 if (ext_id == notified_extension_ids_.end())
421 return;
422
423 notified_extension_ids_.erase(ext_id);
424 message_center::MessageCenter::Get()->RemoveNotification(
425 GetNotificationId(extension_id), false);
426 }
427
428 void ExtensionStorageMonitor::RemoveAllNotifications() {
429 message_center::MessageCenter* center = message_center::MessageCenter::Get();
430 DCHECK(center);
431 for (std::set<std::string>::iterator it = notified_extension_ids_.begin();
432 it != notified_extension_ids_.end(); ++it) {
433 center->RemoveNotification(GetNotificationId(*it), false);
434 }
435 notified_extension_ids_.clear();
436 }
437
438 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698