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_service.h" 14 #include "chrome/browser/extensions/extension_storage_monitor_factory.h" 15 #include "chrome/browser/extensions/extension_util.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/common/extensions/manifest_handlers/app_launch_info.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/browser/extension_system.h" 26 #include "extensions/browser/image_loader.h" 27 #include "extensions/common/extension.h" 28 #include "extensions/common/manifest_handlers/icons_handler.h" 29 #include "extensions/common/permissions/permissions_data.h" 30 #include "grit/generated_resources.h" 31 #include "ui/base/l10n/l10n_util.h" 32 #include "ui/message_center/message_center.h" 33 #include "ui/message_center/notifier_settings.h" 34 #include "ui/message_center/views/constants.h" 35 #include "webkit/browser/quota/quota_manager.h" 36 #include "webkit/browser/quota/storage_observer.h" 37 38 using content::BrowserThread; 39 40 namespace extensions { 41 42 namespace { 43 44 // The rate at which we would like to observe storage events. 45 const int kStorageEventRateSec = 30; 46 47 // The storage type to monitor. 48 const quota::StorageType kMonitorStorageType = quota::kStorageTypePersistent; 49 50 // Set the thresholds for the first notification. Ephemeral apps have a lower 51 // threshold than installed extensions and apps. Once a threshold is exceeded, 52 // it will be doubled to throttle notifications. 53 const int64 kMBytes = 1024 * 1024; 54 const int64 kEphemeralAppInitialThreshold = 250 * kMBytes; 55 const int64 kExtensionInitialThreshold = 1000 * kMBytes; 56 57 // Notifications have an ID so that we can update them. 58 const char kNotificationIdFormat[] = "ExtensionStorageMonitor-$1-$2"; 59 const char kSystemNotifierId[] = "ExtensionStorageMonitor"; 60 61 // A preference that stores the next threshold for displaying a notification 62 // when an extension or app consumes excessive disk space. This will not be 63 // set until the extension/app reaches the initial threshold. 64 const char kPrefNextStorageThreshold[] = "next_storage_threshold"; 65 66 // If this preference is set to true, notifications will be suppressed when an 67 // extension or app consumes excessive disk space. 68 const char kPrefDisableStorageNotifications[] = "disable_storage_notifications"; 69 70 bool ShouldMonitorStorageFor(const Extension* extension) { 71 // Only monitor storage for extensions that are granted unlimited storage. 72 // Do not monitor storage for component extensions. 73 return extension->permissions_data()->HasAPIPermission( 74 APIPermission::kUnlimitedStorage) && 75 extension->location() != Manifest::COMPONENT; 76 } 77 78 const Extension* GetExtensionById(content::BrowserContext* context, 79 const std::string& extension_id) { 80 return ExtensionRegistry::Get(context)->GetExtensionById( 81 extension_id, ExtensionRegistry::EVERYTHING); 82 } 83 84 } // namespace 85 86 // StorageEventObserver monitors the storage usage of extensions and lives on 87 // the IO thread. When a threshold is exceeded, a message will be posted to the 88 // UI thread, which displays the notification. 89 class StorageEventObserver 90 : public base::RefCountedThreadSafe< 91 StorageEventObserver, 92 BrowserThread::DeleteOnIOThread>, 93 public quota::StorageObserver { 94 public: 95 explicit StorageEventObserver( 96 base::WeakPtr<ExtensionStorageMonitor> storage_monitor) 97 : storage_monitor_(storage_monitor) { 98 } 99 100 // Register as an observer for the extension's storage events. 101 void StartObservingForExtension( 102 scoped_refptr<quota::QuotaManager> quota_manager, 103 const std::string& extension_id, 104 const GURL& site_url, 105 int64 next_threshold, 106 int rate) { 107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 108 DCHECK(quota_manager.get()); 109 110 GURL origin = site_url.GetOrigin(); 111 StorageState& state = origin_state_map_[origin]; 112 state.quota_manager = quota_manager; 113 state.extension_id = extension_id; 114 state.next_threshold = next_threshold; 115 116 quota::StorageObserver::MonitorParams params( 117 kMonitorStorageType, 118 origin, 119 base::TimeDelta::FromSeconds(rate), 120 false); 121 quota_manager->AddStorageObserver(this, params); 122 } 123 124 // Updates the threshold for an extension already being monitored. 125 void UpdateThresholdForExtension(const std::string& extension_id, 126 int64 next_threshold) { 127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 128 129 for (OriginStorageStateMap::iterator it = origin_state_map_.begin(); 130 it != origin_state_map_.end(); 131 ++it) { 132 if (it->second.extension_id == extension_id) { 133 it->second.next_threshold = next_threshold; 134 break; 135 } 136 } 137 } 138 139 // Deregister as an observer for the extension's storage events. 140 void StopObservingForExtension(const std::string& extension_id) { 141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 142 143 for (OriginStorageStateMap::iterator it = origin_state_map_.begin(); 144 it != origin_state_map_.end(); ) { 145 if (it->second.extension_id == extension_id) { 146 quota::StorageObserver::Filter filter(kMonitorStorageType, it->first); 147 it->second.quota_manager->RemoveStorageObserverForFilter(this, filter); 148 origin_state_map_.erase(it++); 149 } else { 150 ++it; 151 } 152 } 153 } 154 155 // Stop observing all storage events. Called during shutdown. 156 void StopObserving() { 157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 158 159 for (OriginStorageStateMap::iterator it = origin_state_map_.begin(); 160 it != origin_state_map_.end(); ++it) { 161 it->second.quota_manager->RemoveStorageObserver(this); 162 } 163 origin_state_map_.clear(); 164 } 165 166 private: 167 friend class base::DeleteHelper<StorageEventObserver>; 168 friend struct content::BrowserThread::DeleteOnThread< 169 content::BrowserThread::IO>; 170 171 struct StorageState { 172 scoped_refptr<quota::QuotaManager> quota_manager; 173 std::string extension_id; 174 int64 next_threshold; 175 176 StorageState() : next_threshold(0) {} 177 }; 178 typedef std::map<GURL, StorageState> OriginStorageStateMap; 179 180 virtual ~StorageEventObserver() { 181 DCHECK(origin_state_map_.empty()); 182 StopObserving(); 183 } 184 185 // quota::StorageObserver implementation. 186 virtual void OnStorageEvent(const Event& event) OVERRIDE { 187 OriginStorageStateMap::iterator state = 188 origin_state_map_.find(event.filter.origin); 189 if (state == origin_state_map_.end()) 190 return; 191 192 if (event.usage >= state->second.next_threshold) { 193 while (event.usage >= state->second.next_threshold) 194 state->second.next_threshold *= 2; 195 196 BrowserThread::PostTask( 197 BrowserThread::UI, 198 FROM_HERE, 199 base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded, 200 storage_monitor_, 201 state->second.extension_id, 202 state->second.next_threshold, 203 event.usage)); 204 } 205 } 206 207 OriginStorageStateMap origin_state_map_; 208 base::WeakPtr<ExtensionStorageMonitor> storage_monitor_; 209 }; 210 211 // ExtensionStorageMonitor 212 213 // static 214 ExtensionStorageMonitor* ExtensionStorageMonitor::Get( 215 content::BrowserContext* context) { 216 return ExtensionStorageMonitorFactory::GetForBrowserContext(context); 217 } 218 219 ExtensionStorageMonitor::ExtensionStorageMonitor( 220 content::BrowserContext* context) 221 : enable_for_all_extensions_(false), 222 initial_extension_threshold_(kExtensionInitialThreshold), 223 initial_ephemeral_threshold_(kEphemeralAppInitialThreshold), 224 observer_rate_(kStorageEventRateSec), 225 context_(context), 226 extension_prefs_(ExtensionPrefs::Get(context)), 227 extension_registry_observer_(this), 228 weak_ptr_factory_(this) { 229 DCHECK(extension_prefs_); 230 231 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, 232 content::Source<content::BrowserContext>(context_)); 233 234 extension_registry_observer_.Add(ExtensionRegistry::Get(context_)); 235 } 236 237 ExtensionStorageMonitor::~ExtensionStorageMonitor() {} 238 239 void ExtensionStorageMonitor::Observe( 240 int type, 241 const content::NotificationSource& source, 242 const content::NotificationDetails& details) { 243 switch (type) { 244 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 245 StopMonitoringAll(); 246 break; 247 } 248 default: 249 NOTREACHED(); 250 } 251 } 252 253 void ExtensionStorageMonitor::OnExtensionLoaded( 254 content::BrowserContext* browser_context, 255 const Extension* extension) { 256 StartMonitoringStorage(extension); 257 } 258 259 void ExtensionStorageMonitor::OnExtensionUnloaded( 260 content::BrowserContext* browser_context, 261 const Extension* extension, 262 UnloadedExtensionInfo::Reason reason) { 263 StopMonitoringStorage(extension->id()); 264 } 265 266 void ExtensionStorageMonitor::OnExtensionWillBeInstalled( 267 content::BrowserContext* browser_context, 268 const Extension* extension, 269 bool is_update, 270 bool from_ephemeral, 271 const std::string& old_name) { 272 // If an ephemeral app was promoted to a regular installed app, we may need to 273 // increase its next threshold. 274 if (!from_ephemeral || !ShouldMonitorStorageFor(extension)) 275 return; 276 277 if (!enable_for_all_extensions_) { 278 // If monitoring is not enabled for installed extensions, just stop 279 // monitoring. 280 SetNextStorageThreshold(extension->id(), 0); 281 StopMonitoringStorage(extension->id()); 282 return; 283 } 284 285 int64 next_threshold = GetNextStorageThresholdFromPrefs(extension->id()); 286 if (next_threshold <= initial_extension_threshold_) { 287 // Clear the next threshold in the prefs. This effectively raises it to 288 // |initial_extension_threshold_|. If the current threshold is already 289 // higher than this, leave it as is. 290 SetNextStorageThreshold(extension->id(), 0); 291 292 if (storage_observer_.get()) { 293 BrowserThread::PostTask( 294 BrowserThread::IO, 295 FROM_HERE, 296 base::Bind(&StorageEventObserver::UpdateThresholdForExtension, 297 storage_observer_, 298 extension->id(), 299 initial_extension_threshold_)); 300 } 301 } 302 } 303 304 void ExtensionStorageMonitor::OnExtensionUninstalled( 305 content::BrowserContext* browser_context, 306 const Extension* extension) { 307 RemoveNotificationForExtension(extension->id()); 308 } 309 310 void ExtensionStorageMonitor::ExtensionUninstallAccepted() { 311 DCHECK(!uninstall_extension_id_.empty()); 312 313 const Extension* extension = GetExtensionById(context_, 314 uninstall_extension_id_); 315 uninstall_extension_id_.clear(); 316 if (!extension) 317 return; 318 319 ExtensionService* service = 320 ExtensionSystem::Get(context_)->extension_service(); 321 DCHECK(service); 322 service->UninstallExtension(extension->id(), false, NULL); 323 } 324 325 void ExtensionStorageMonitor::ExtensionUninstallCanceled() { 326 uninstall_extension_id_.clear(); 327 } 328 329 std::string ExtensionStorageMonitor::GetNotificationId( 330 const std::string& extension_id) { 331 std::vector<std::string> placeholders; 332 placeholders.push_back(context_->GetPath().BaseName().MaybeAsASCII()); 333 placeholders.push_back(extension_id); 334 335 return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL); 336 } 337 338 void ExtensionStorageMonitor::OnStorageThresholdExceeded( 339 const std::string& extension_id, 340 int64 next_threshold, 341 int64 current_usage) { 342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 343 344 const Extension* extension = GetExtensionById(context_, extension_id); 345 if (!extension) 346 return; 347 348 if (GetNextStorageThreshold(extension->id()) < next_threshold) 349 SetNextStorageThreshold(extension->id(), next_threshold); 350 351 const int kIconSize = message_center::kNotificationIconSize; 352 ExtensionResource resource = IconsInfo::GetIconResource( 353 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER); 354 ImageLoader::Get(context_)->LoadImageAsync( 355 extension, resource, gfx::Size(kIconSize, kIconSize), 356 base::Bind(&ExtensionStorageMonitor::OnImageLoaded, 357 weak_ptr_factory_.GetWeakPtr(), 358 extension_id, 359 current_usage)); 360 } 361 362 void ExtensionStorageMonitor::OnImageLoaded( 363 const std::string& extension_id, 364 int64 current_usage, 365 const gfx::Image& image) { 366 const Extension* extension = GetExtensionById(context_, extension_id); 367 if (!extension) 368 return; 369 370 // Remove any existing notifications to force a new notification to pop up. 371 std::string notification_id(GetNotificationId(extension_id)); 372 message_center::MessageCenter::Get()->RemoveNotification( 373 notification_id, false); 374 375 message_center::RichNotificationData notification_data; 376 notification_data.buttons.push_back(message_center::ButtonInfo( 377 l10n_util::GetStringUTF16(extension->is_app() ? 378 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP : 379 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION))); 380 notification_data.buttons.push_back(message_center::ButtonInfo( 381 l10n_util::GetStringUTF16(extension->is_app() ? 382 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_APP : 383 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_EXTENSION))); 384 385 gfx::Image notification_image(image); 386 if (notification_image.IsEmpty()) { 387 notification_image = 388 extension->is_app() ? gfx::Image(util::GetDefaultAppIcon()) 389 : gfx::Image(util::GetDefaultExtensionIcon()); 390 } 391 392 scoped_ptr<message_center::Notification> notification; 393 notification.reset(new message_center::Notification( 394 message_center::NOTIFICATION_TYPE_SIMPLE, 395 notification_id, 396 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE), 397 l10n_util::GetStringFUTF16( 398 IDS_EXTENSION_STORAGE_MONITOR_TEXT, 399 base::UTF8ToUTF16(extension->name()), 400 base::IntToString16(current_usage / kMBytes)), 401 notification_image, 402 base::string16() /* display source */, 403 message_center::NotifierId( 404 message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId), 405 notification_data, 406 new message_center::HandleNotificationButtonClickDelegate(base::Bind( 407 &ExtensionStorageMonitor::OnNotificationButtonClick, 408 weak_ptr_factory_.GetWeakPtr(), 409 extension_id)))); 410 notification->SetSystemPriority(); 411 message_center::MessageCenter::Get()->AddNotification(notification.Pass()); 412 413 notified_extension_ids_.insert(extension_id); 414 } 415 416 void ExtensionStorageMonitor::OnNotificationButtonClick( 417 const std::string& extension_id, int button_index) { 418 switch (button_index) { 419 case BUTTON_DISABLE_NOTIFICATION: { 420 DisableStorageMonitoring(extension_id); 421 break; 422 } 423 case BUTTON_UNINSTALL: { 424 ShowUninstallPrompt(extension_id); 425 break; 426 } 427 default: 428 NOTREACHED(); 429 } 430 } 431 432 void ExtensionStorageMonitor::DisableStorageMonitoring( 433 const std::string& extension_id) { 434 StopMonitoringStorage(extension_id); 435 436 SetStorageNotificationEnabled(extension_id, false); 437 438 message_center::MessageCenter::Get()->RemoveNotification( 439 GetNotificationId(extension_id), false); 440 } 441 442 void ExtensionStorageMonitor::StartMonitoringStorage( 443 const Extension* extension) { 444 if (!ShouldMonitorStorageFor(extension)) 445 return; 446 447 // First apply this feature only to experimental ephemeral apps. If it works 448 // well, roll it out to all extensions and apps. 449 if (!enable_for_all_extensions_ && 450 !extension_prefs_->IsEphemeralApp(extension->id())) { 451 return; 452 } 453 454 if (!IsStorageNotificationEnabled(extension->id())) 455 return; 456 457 // Lazily create the storage monitor proxy on the IO thread. 458 if (!storage_observer_.get()) { 459 storage_observer_ = 460 new StorageEventObserver(weak_ptr_factory_.GetWeakPtr()); 461 } 462 463 GURL site_url = 464 extensions::util::GetSiteForExtensionId(extension->id(), context_); 465 content::StoragePartition* storage_partition = 466 content::BrowserContext::GetStoragePartitionForSite(context_, site_url); 467 DCHECK(storage_partition); 468 scoped_refptr<quota::QuotaManager> quota_manager( 469 storage_partition->GetQuotaManager()); 470 471 GURL storage_origin(site_url.GetOrigin()); 472 if (extension->is_hosted_app()) 473 storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin(); 474 475 BrowserThread::PostTask( 476 BrowserThread::IO, 477 FROM_HERE, 478 base::Bind(&StorageEventObserver::StartObservingForExtension, 479 storage_observer_, 480 quota_manager, 481 extension->id(), 482 storage_origin, 483 GetNextStorageThreshold(extension->id()), 484 observer_rate_)); 485 } 486 487 void ExtensionStorageMonitor::StopMonitoringStorage( 488 const std::string& extension_id) { 489 if (!storage_observer_.get()) 490 return; 491 492 BrowserThread::PostTask( 493 BrowserThread::IO, 494 FROM_HERE, 495 base::Bind(&StorageEventObserver::StopObservingForExtension, 496 storage_observer_, 497 extension_id)); 498 } 499 500 void ExtensionStorageMonitor::StopMonitoringAll() { 501 extension_registry_observer_.RemoveAll(); 502 503 RemoveAllNotifications(); 504 505 if (!storage_observer_.get()) 506 return; 507 508 BrowserThread::PostTask( 509 BrowserThread::IO, 510 FROM_HERE, 511 base::Bind(&StorageEventObserver::StopObserving, storage_observer_)); 512 storage_observer_ = NULL; 513 } 514 515 void ExtensionStorageMonitor::RemoveNotificationForExtension( 516 const std::string& extension_id) { 517 std::set<std::string>::iterator ext_id = 518 notified_extension_ids_.find(extension_id); 519 if (ext_id == notified_extension_ids_.end()) 520 return; 521 522 notified_extension_ids_.erase(ext_id); 523 message_center::MessageCenter::Get()->RemoveNotification( 524 GetNotificationId(extension_id), false); 525 } 526 527 void ExtensionStorageMonitor::RemoveAllNotifications() { 528 if (notified_extension_ids_.empty()) 529 return; 530 531 message_center::MessageCenter* center = message_center::MessageCenter::Get(); 532 DCHECK(center); 533 for (std::set<std::string>::iterator it = notified_extension_ids_.begin(); 534 it != notified_extension_ids_.end(); ++it) { 535 center->RemoveNotification(GetNotificationId(*it), false); 536 } 537 notified_extension_ids_.clear(); 538 } 539 540 void ExtensionStorageMonitor::ShowUninstallPrompt( 541 const std::string& extension_id) { 542 const Extension* extension = GetExtensionById(context_, extension_id); 543 if (!extension) 544 return; 545 546 if (!uninstall_dialog_.get()) { 547 uninstall_dialog_.reset(ExtensionUninstallDialog::Create( 548 Profile::FromBrowserContext(context_), NULL, this)); 549 } 550 551 uninstall_extension_id_ = extension->id(); 552 uninstall_dialog_->ConfirmUninstall(extension); 553 } 554 555 int64 ExtensionStorageMonitor::GetNextStorageThreshold( 556 const std::string& extension_id) const { 557 int next_threshold = GetNextStorageThresholdFromPrefs(extension_id); 558 if (next_threshold == 0) { 559 // The next threshold is written to the prefs after the initial threshold is 560 // exceeded. 561 next_threshold = extension_prefs_->IsEphemeralApp(extension_id) 562 ? initial_ephemeral_threshold_ 563 : initial_extension_threshold_; 564 } 565 return next_threshold; 566 } 567 568 void ExtensionStorageMonitor::SetNextStorageThreshold( 569 const std::string& extension_id, 570 int64 next_threshold) { 571 extension_prefs_->UpdateExtensionPref( 572 extension_id, 573 kPrefNextStorageThreshold, 574 next_threshold > 0 575 ? new base::StringValue(base::Int64ToString(next_threshold)) 576 : NULL); 577 } 578 579 int64 ExtensionStorageMonitor::GetNextStorageThresholdFromPrefs( 580 const std::string& extension_id) const { 581 std::string next_threshold_str; 582 if (extension_prefs_->ReadPrefAsString( 583 extension_id, kPrefNextStorageThreshold, &next_threshold_str)) { 584 int64 next_threshold; 585 if (base::StringToInt64(next_threshold_str, &next_threshold)) 586 return next_threshold; 587 } 588 589 // A return value of zero indicates that the initial threshold has not yet 590 // been reached. 591 return 0; 592 } 593 594 bool ExtensionStorageMonitor::IsStorageNotificationEnabled( 595 const std::string& extension_id) const { 596 bool disable_notifications; 597 if (extension_prefs_->ReadPrefAsBoolean(extension_id, 598 kPrefDisableStorageNotifications, 599 &disable_notifications)) { 600 return !disable_notifications; 601 } 602 603 return true; 604 } 605 606 void ExtensionStorageMonitor::SetStorageNotificationEnabled( 607 const std::string& extension_id, 608 bool enable_notifications) { 609 extension_prefs_->UpdateExtensionPref( 610 extension_id, 611 kPrefDisableStorageNotifications, 612 enable_notifications ? NULL : new base::FundamentalValue(true)); 613 } 614 615 } // namespace extensions 616