Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2609)

Unified Diff: chrome/browser/extensions/extension_storage_monitor.cc

Issue 221933013: Show a notification when an ephemeral app consumes excessive disk space (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@webkit_storage_monitor
Patch Set: Fix test failures Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/extensions/extension_storage_monitor.cc
diff --git a/chrome/browser/extensions/extension_storage_monitor.cc b/chrome/browser/extensions/extension_storage_monitor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fbc173432d236acdad56b8e0c292745b6945af33
--- /dev/null
+++ b/chrome/browser/extensions/extension_storage_monitor.cc
@@ -0,0 +1,453 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_storage_monitor.h"
+
+#include <map>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/extension_storage_monitor_factory.h"
+#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/image_loader.h"
+#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "content/public/browser/storage_partition.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notifier_settings.h"
+#include "ui/message_center/views/constants.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/browser/quota/storage_observer.h"
+
+using content::BrowserThread;
+
+namespace extensions {
+
+namespace {
+
+// The rate at which we would like to observe storage events.
+const int kStorageEventRateSec = 30;
+
+// The storage type to monitor.
+const quota::StorageType kMonitorStorageType = quota::kStorageTypePersistent;
+
+// Set the thresholds for the first notification. Ephemeral apps have a lower
+// threshold than installed extensions and apps. Once a threshold is exceeded,
+// it will be doubled to throttle notifications.
+const int64 kMBytes = 1024 * 1024;
+const int64 kEphemeralAppInitialThreshold = 250 * kMBytes;
+const int64 kExtensionInitialThreshold = 1000 * kMBytes;
+
+// Notifications have an ID so that we can update them.
+const char kNotificationIdFormat[] = "ExtensionStorageMonitor-$1-$2";
+const char kSystemNotifierId[] = "ExtensionStorageMonitor";
+
+} // namespace
+
+// StorageEventObserver monitors the storage usage of extensions and lives on
+// the IO thread. When a threshold is exceeded, a message will be posted to the
+// UI thread, which displays the notification.
+class StorageEventObserver
+ : public base::RefCountedThreadSafe<
+ StorageEventObserver,
+ BrowserThread::DeleteOnIOThread>,
+ public quota::StorageObserver {
+ public:
+ explicit StorageEventObserver(
+ base::WeakPtr<ExtensionStorageMonitor> storage_monitor)
+ : storage_monitor_(storage_monitor) {
+ }
+
+ // Register as an observer for the extension's storage events.
+ void StartObservingForExtension(
+ scoped_refptr<quota::QuotaManager> quota_manager,
+ const std::string& extension_id,
+ const GURL& site_url,
+ int64 next_threshold,
+ int rate) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(quota_manager.get());
+
+ GURL origin = site_url.GetOrigin();
+ StorageState& state = origin_state_map_[origin];
+ state.quota_manager = quota_manager;
+ state.extension_id = extension_id;
+ state.next_threshold = next_threshold;
+
+ quota::StorageObserver::MonitorParams params(
+ kMonitorStorageType,
+ origin,
+ base::TimeDelta::FromSeconds(rate),
+ false);
+ quota_manager->AddStorageObserver(this, params);
+ }
+
+ // Deregister as an observer for the extension's storage events.
+ void StopObservingForExtension(const std::string& extension_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
+ it != origin_state_map_.end(); ) {
+ if (it->second.extension_id == extension_id) {
+ quota::StorageObserver::Filter filter(kMonitorStorageType, it->first);
+ it->second.quota_manager->RemoveStorageObserverForFilter(this, filter);
+ origin_state_map_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ // Stop observing all storage events. Called during shutdown.
+ void StopObserving() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
+ it != origin_state_map_.end(); ++it) {
+ it->second.quota_manager->RemoveStorageObserver(this);
+ }
+ origin_state_map_.clear();
+ }
+
+ private:
+ friend class base::DeleteHelper<StorageEventObserver>;
+ friend struct content::BrowserThread::DeleteOnThread<
+ content::BrowserThread::IO>;
+
+ struct StorageState {
+ scoped_refptr<quota::QuotaManager> quota_manager;
+ std::string extension_id;
+ int64 next_threshold;
+
+ StorageState() : next_threshold(0) {}
+ };
+ typedef std::map<GURL, StorageState> OriginStorageStateMap;
+
+ virtual ~StorageEventObserver() {
+ DCHECK(origin_state_map_.empty());
+ StopObserving();
+ }
+
+ // quota::StorageObserver implementation.
+ virtual void OnStorageEvent(const Event& event) OVERRIDE {
+ OriginStorageStateMap::iterator state =
+ origin_state_map_.find(event.filter.origin);
+ if (state == origin_state_map_.end())
+ return;
+
+ if (event.usage >= state->second.next_threshold) {
+ while (event.usage >= state->second.next_threshold)
+ state->second.next_threshold *= 2;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded,
+ storage_monitor_,
+ state->second.extension_id,
+ state->second.next_threshold,
+ event.usage));
+ }
+ }
+
+ OriginStorageStateMap origin_state_map_;
+ base::WeakPtr<ExtensionStorageMonitor> storage_monitor_;
+};
+
+// ExtensionStorageMonitor
+
+// static
+ExtensionStorageMonitor* ExtensionStorageMonitor::Get(
+ content::BrowserContext* context) {
+ return ExtensionStorageMonitorFactory::GetForBrowserContext(context);
+}
+
+ExtensionStorageMonitor::ExtensionStorageMonitor(
+ content::BrowserContext* context)
+ : enable_for_all_extensions_(false),
+ initial_extension_threshold_(kExtensionInitialThreshold),
+ initial_ephemeral_threshold_(kEphemeralAppInitialThreshold),
+ observer_rate_(kStorageEventRateSec),
+ context_(context),
+ weak_ptr_factory_(this) {
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
+ content::Source<content::BrowserContext>(context_));
+ registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
+ content::Source<content::BrowserContext>(context_));
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ DCHECK(registry);
+ registry->AddObserver(this);
+}
+
+ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
+
+void ExtensionStorageMonitor::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
+ const Extension* extension =
+ content::Details<const Extension>(details).ptr();
+ RemoveNotificationForExtension(extension->id());
+ break;
+ }
+ case chrome::NOTIFICATION_PROFILE_DESTROYED: {
+ StopMonitoringAll();
+ break;
+ }
+ default:
+ NOTREACHED();
+ };
+}
+
+void ExtensionStorageMonitor::OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ DCHECK(extension);
+ StartMonitoringStorage(extension);
+}
+
+void ExtensionStorageMonitor::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ DCHECK(extension);
+ StopMonitoringStorage(extension->id());
+}
+
+std::string ExtensionStorageMonitor::GetNotificationId(
+ const std::string& extension_id) {
+ std::vector<std::string> placeholders;
+ placeholders.push_back(context_->GetPath().BaseName().MaybeAsASCII());
+ placeholders.push_back(extension_id);
+
+ return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL);
+}
+
+void ExtensionStorageMonitor::OnStorageThresholdExceeded(
+ const std::string& extension_id,
+ int64 next_threshold,
+ int64 current_usage) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ const Extension* extension = ExtensionRegistry::Get(context_)->
+ GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+ if (!extension)
+ return;
+
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
+ DCHECK(prefs);
+ prefs->SetNextStorageThreshold(extension->id(), next_threshold);
+
+ const int kIconSize = message_center::kNotificationIconSize;
+ ExtensionResource resource = IconsInfo::GetIconResource(
+ extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
+ ImageLoader::Get(context_)->LoadImageAsync(
+ extension, resource, gfx::Size(kIconSize, kIconSize),
+ base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id,
+ current_usage));
+}
+
+void ExtensionStorageMonitor::OnImageLoaded(
+ const std::string& extension_id,
+ int64 current_usage,
+ const gfx::Image& image) {
+ const Extension* extension = ExtensionRegistry::Get(context_)->
+ GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+ if (!extension)
+ return;
+
+ // Remove any existing notifications to force a new notification to pop up.
+ std::string notification_id(GetNotificationId(extension_id));
+ message_center::MessageCenter::Get()->RemoveNotification(
+ notification_id, false);
+
+ message_center::RichNotificationData notification_data;
+ notification_data.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringUTF16(extension->is_app() ?
+ IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
+ IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
+
+ gfx::Image notification_image(image);
+ if (notification_image.IsEmpty()) {
+ notification_image =
+ extension->is_app() ? gfx::Image(IconsInfo::GetDefaultAppIcon())
+ : gfx::Image(IconsInfo::GetDefaultExtensionIcon());
+ }
+
+ scoped_ptr<message_center::Notification> notification;
+ notification.reset(new message_center::Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ notification_id,
+ l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
+ l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_STORAGE_MONITOR_TEXT,
+ base::UTF8ToUTF16(extension->name()),
+ base::IntToString16(current_usage / kMBytes)),
+ notification_image,
+ base::string16() /* display source */,
+ message_center::NotifierId(
+ message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
+ notification_data,
+ new message_center::HandleNotificationButtonClickDelegate(base::Bind(
+ &ExtensionStorageMonitor::OnNotificationButtonClick,
+ weak_ptr_factory_.GetWeakPtr(),
+ extension_id))));
+ notification->SetSystemPriority();
+ message_center::MessageCenter::Get()->AddNotification(notification.Pass());
+
+ notified_extension_ids_.insert(extension_id);
+}
+
+void ExtensionStorageMonitor::OnNotificationButtonClick(
+ const std::string& extension_id, int button_index) {
+ switch (button_index) {
+ case BUTTON_DISABLE_NOTIFICATION: {
+ DisableStorageMonitoring(extension_id);
+ break;
+ }
+ default:
+ NOTREACHED();
+ };
+}
+
+void ExtensionStorageMonitor::DisableStorageMonitoring(
+ const std::string& extension_id) {
+ StopMonitoringStorage(extension_id);
+
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
+ DCHECK(prefs);
+ prefs->SetStorageNotificationEnabled(extension_id, false);
+
+ message_center::MessageCenter::Get()->RemoveNotification(
+ GetNotificationId(extension_id), false);
+}
+
+void ExtensionStorageMonitor::StartMonitoringStorage(
+ const Extension* extension) {
+ if (!extension->HasAPIPermission(APIPermission::kUnlimitedStorage))
+ return;
+
+ // Do not monitor storage for component extensions.
+ if (extension->location() == Manifest::COMPONENT)
+ return;
+
+ // First apply this feature only to experimental ephemeral apps. If it works
+ // well, roll it out to all extensions and apps.
+ if (!extension->is_ephemeral() && !enable_for_all_extensions_)
+ return;
+
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
+ DCHECK(prefs);
+ if (!prefs->IsStorageNotificationEnabled(extension->id()))
+ return;
+
+ // Lazily create the storage monitor proxy on the IO thread.
+ if (!storage_observer_.get()) {
+ storage_observer_ =
+ new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
+ }
+
+ GURL site_url =
+ extensions::util::GetSiteForExtensionId(extension->id(), context_);
+ content::StoragePartition* storage_partition =
+ content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
+ DCHECK(storage_partition);
+ scoped_refptr<quota::QuotaManager> quota_manager(
+ storage_partition->GetQuotaManager());
+
+ GURL storage_origin(site_url.GetOrigin());
+ if (extension->is_hosted_app())
+ storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
+
+ int next_threshold = prefs->GetNextStorageThreshold(extension->id());
+ if (next_threshold == 0) {
+ // The next threshold is written to the prefs after the initial threshold is
+ // exceeded.
+ next_threshold = extension->is_ephemeral() ? initial_ephemeral_threshold_
+ : initial_extension_threshold_;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&StorageEventObserver::StartObservingForExtension,
+ storage_observer_,
+ quota_manager,
+ extension->id(),
+ storage_origin,
+ next_threshold,
+ observer_rate_));
+}
+
+void ExtensionStorageMonitor::StopMonitoringStorage(
+ const std::string& extension_id) {
+ if (!storage_observer_.get())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&StorageEventObserver::StopObservingForExtension,
+ storage_observer_,
+ extension_id));
+}
+
+void ExtensionStorageMonitor::StopMonitoringAll() {
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ DCHECK(registry);
+ registry->RemoveObserver(this);
+
+ RemoveAllNotifications();
+
+ if (!storage_observer_.get())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
+ storage_observer_ = NULL;
+}
+
+void ExtensionStorageMonitor::RemoveNotificationForExtension(
+ const std::string& extension_id) {
+ std::set<std::string>::iterator ext_id =
+ notified_extension_ids_.find(extension_id);
+ if (ext_id == notified_extension_ids_.end())
+ return;
+
+ notified_extension_ids_.erase(ext_id);
+ message_center::MessageCenter::Get()->RemoveNotification(
+ GetNotificationId(extension_id), false);
+}
+
+void ExtensionStorageMonitor::RemoveAllNotifications() {
+ if (notified_extension_ids_.empty())
+ return;
+
+ message_center::MessageCenter* center = message_center::MessageCenter::Get();
+ DCHECK(center);
+ for (std::set<std::string>::iterator it = notified_extension_ids_.begin();
+ it != notified_extension_ids_.end(); ++it) {
+ center->RemoveNotification(GetNotificationId(*it), false);
+ }
+ notified_extension_ids_.clear();
+}
+
+} // namespace extensions

Powered by Google App Engine
This is Rietveld 408576698