Home | History | Annotate | Download | only in extensions
      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