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