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