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++); | |
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 | |
OLD | NEW |