OLD | NEW |
---|---|
(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 | |
OLD | NEW |