| 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
|
|
|