Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / extensions / extension_storage_monitor.cc
blob4a0cd83a3b88a389fc4b5bf65eedc0dce187d430
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.
5 #include "chrome/browser/extensions/extension_storage_monitor.h"
7 #include <map>
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_storage_monitor_factory.h"
16 #include "chrome/browser/extensions/extension_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "content/public/browser/browser_context.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/storage_partition.h"
25 #include "extensions/browser/extension_prefs.h"
26 #include "extensions/browser/extension_registry.h"
27 #include "extensions/browser/extension_system.h"
28 #include "extensions/browser/image_loader.h"
29 #include "extensions/browser/uninstall_reason.h"
30 #include "extensions/common/extension.h"
31 #include "extensions/common/manifest_handlers/icons_handler.h"
32 #include "extensions/common/permissions/permissions_data.h"
33 #include "storage/browser/quota/quota_manager.h"
34 #include "storage/browser/quota/storage_observer.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/message_center/message_center.h"
37 #include "ui/message_center/notifier_settings.h"
38 #include "ui/message_center/views/constants.h"
40 using content::BrowserThread;
42 namespace extensions {
44 namespace {
46 // The rate at which we would like to observe storage events.
47 const int kStorageEventRateSec = 30;
49 // Set the thresholds for the first notification. Ephemeral apps have a lower
50 // threshold than installed extensions and apps. Once a threshold is exceeded,
51 // it will be doubled to throttle notifications.
52 const int64 kMBytes = 1024 * 1024;
53 const int64 kEphemeralAppInitialThreshold = 250 * kMBytes;
54 const int64 kExtensionInitialThreshold = 1000 * kMBytes;
56 // Notifications have an ID so that we can update them.
57 const char kNotificationIdFormat[] = "ExtensionStorageMonitor-$1-$2";
58 const char kSystemNotifierId[] = "ExtensionStorageMonitor";
60 // A preference that stores the next threshold for displaying a notification
61 // when an extension or app consumes excessive disk space. This will not be
62 // set until the extension/app reaches the initial threshold.
63 const char kPrefNextStorageThreshold[] = "next_storage_threshold";
65 // If this preference is set to true, notifications will be suppressed when an
66 // extension or app consumes excessive disk space.
67 const char kPrefDisableStorageNotifications[] = "disable_storage_notifications";
69 bool ShouldMonitorStorageFor(const Extension* extension) {
70 // Only monitor storage for extensions that are granted unlimited storage.
71 // Do not monitor storage for component extensions.
72 return extension->permissions_data()->HasAPIPermission(
73 APIPermission::kUnlimitedStorage) &&
74 extension->location() != Manifest::COMPONENT;
77 bool ShouldGatherMetricsFor(const Extension* extension) {
78 // We want to know the usage of hosted apps' storage.
79 return ShouldMonitorStorageFor(extension) && extension->is_hosted_app();
82 const Extension* GetExtensionById(content::BrowserContext* context,
83 const std::string& extension_id) {
84 return ExtensionRegistry::Get(context)->GetExtensionById(
85 extension_id, ExtensionRegistry::EVERYTHING);
88 void LogTemporaryStorageUsage(int64 usage,
89 storage::QuotaStatusCode status,
90 int64 global_quota) {
91 if (status == storage::kQuotaStatusOk) {
92 int64 per_app_quota =
93 global_quota / storage::QuotaManager::kPerHostTemporaryPortion;
94 // Note we use COUNTS_100 (instead of PERCENT) because this can potentially
95 // exceed 100%.
96 UMA_HISTOGRAM_COUNTS_100(
97 "Extensions.HostedAppUnlimitedStorageTemporaryStorageUsage",
98 100.0 * usage / per_app_quota);
102 } // namespace
104 // StorageEventObserver monitors the storage usage of extensions and lives on
105 // the IO thread. When a threshold is exceeded, a message will be posted to the
106 // UI thread, which displays the notification.
107 class StorageEventObserver
108 : public base::RefCountedThreadSafe<StorageEventObserver,
109 BrowserThread::DeleteOnIOThread>,
110 public storage::StorageObserver {
111 public:
112 explicit StorageEventObserver(
113 base::WeakPtr<ExtensionStorageMonitor> storage_monitor)
114 : storage_monitor_(storage_monitor) {
117 // Register as an observer for the extension's storage events.
118 void StartObservingForExtension(
119 scoped_refptr<storage::QuotaManager> quota_manager,
120 const std::string& extension_id,
121 const GURL& site_url,
122 int64 next_threshold,
123 const base::TimeDelta& rate,
124 bool should_uma) {
125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
126 DCHECK(quota_manager.get());
128 GURL origin = site_url.GetOrigin();
129 StorageState& state = origin_state_map_[origin];
130 state.quota_manager = quota_manager;
131 state.extension_id = extension_id;
132 state.next_threshold = next_threshold;
133 state.should_uma = should_uma;
135 // We always observe persistent storage usage.
136 storage::StorageObserver::MonitorParams params(
137 storage::kStorageTypePersistent, origin, rate, false);
138 quota_manager->AddStorageObserver(this, params);
139 if (should_uma) {
140 // And if this is for uma, we also observe temporary storage usage.
141 MonitorParams temporary_params(
142 storage::kStorageTypeTemporary, origin, rate, false);
143 quota_manager->AddStorageObserver(this, temporary_params);
147 // Updates the threshold for an extension already being monitored.
148 void UpdateThresholdForExtension(const std::string& extension_id,
149 int64 next_threshold) {
150 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
152 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
153 it != origin_state_map_.end();
154 ++it) {
155 if (it->second.extension_id == extension_id) {
156 it->second.next_threshold = next_threshold;
157 break;
162 // Deregister as an observer for the extension's storage events.
163 void StopObservingForExtension(const std::string& extension_id) {
164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
166 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
167 it != origin_state_map_.end(); ) {
168 if (it->second.extension_id == extension_id) {
169 storage::StorageObserver::Filter filter(
170 storage::kStorageTypePersistent, it->first);
171 it->second.quota_manager->RemoveStorageObserverForFilter(this, filter);
172 // We also need to unregister temporary storage observation, if this was
173 // being tracked for uma.
174 if (it->second.should_uma) {
175 storage::StorageObserver::Filter temporary_filter(
176 storage::kStorageTypeTemporary, it->first);
177 it->second.quota_manager->RemoveStorageObserverForFilter(this,
178 filter);
180 origin_state_map_.erase(it++);
181 } else {
182 ++it;
187 // Stop observing all storage events. Called during shutdown.
188 void StopObserving() {
189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
191 for (OriginStorageStateMap::iterator it = origin_state_map_.begin();
192 it != origin_state_map_.end(); ++it) {
193 it->second.quota_manager->RemoveStorageObserver(this);
195 origin_state_map_.clear();
198 private:
199 friend class base::DeleteHelper<StorageEventObserver>;
200 friend struct content::BrowserThread::DeleteOnThread<
201 content::BrowserThread::IO>;
203 struct StorageState {
204 scoped_refptr<storage::QuotaManager> quota_manager;
206 std::string extension_id;
208 // If |next_threshold| is -1, it signifies that we should not enforce (and
209 // only track) storage for this extension.
210 int64 next_threshold;
212 bool should_uma;
214 StorageState() : next_threshold(-1), should_uma(false) {}
216 typedef std::map<GURL, StorageState> OriginStorageStateMap;
218 virtual ~StorageEventObserver() {
219 DCHECK(origin_state_map_.empty());
220 StopObserving();
223 // storage::StorageObserver implementation.
224 virtual void OnStorageEvent(const Event& event) override {
225 OriginStorageStateMap::iterator iter =
226 origin_state_map_.find(event.filter.origin);
227 if (iter == origin_state_map_.end())
228 return;
229 StorageState& state = iter->second;
231 if (state.should_uma) {
232 if (event.filter.storage_type == storage::kStorageTypePersistent) {
233 UMA_HISTOGRAM_MEMORY_KB(
234 "Extensions.HostedAppUnlimitedStoragePersistentStorageUsage",
235 event.usage);
236 } else {
237 // We can't use the quota in the event because it assumes unlimited
238 // storage.
239 BrowserThread::PostTask(
240 BrowserThread::IO,
241 FROM_HERE,
242 base::Bind(&storage::QuotaManager::GetTemporaryGlobalQuota,
243 state.quota_manager,
244 base::Bind(&LogTemporaryStorageUsage, event.usage)));
248 if (state.next_threshold != -1 &&
249 event.usage >= state.next_threshold) {
250 while (event.usage >= state.next_threshold)
251 state.next_threshold *= 2;
253 BrowserThread::PostTask(
254 BrowserThread::UI,
255 FROM_HERE,
256 base::Bind(&ExtensionStorageMonitor::OnStorageThresholdExceeded,
257 storage_monitor_,
258 state.extension_id,
259 state.next_threshold,
260 event.usage));
264 OriginStorageStateMap origin_state_map_;
265 base::WeakPtr<ExtensionStorageMonitor> storage_monitor_;
268 // ExtensionStorageMonitor
270 // static
271 ExtensionStorageMonitor* ExtensionStorageMonitor::Get(
272 content::BrowserContext* context) {
273 return ExtensionStorageMonitorFactory::GetForBrowserContext(context);
276 ExtensionStorageMonitor::ExtensionStorageMonitor(
277 content::BrowserContext* context)
278 : enable_for_all_extensions_(false),
279 initial_extension_threshold_(kExtensionInitialThreshold),
280 initial_ephemeral_threshold_(kEphemeralAppInitialThreshold),
281 observer_rate_(base::TimeDelta::FromSeconds(kStorageEventRateSec)),
282 context_(context),
283 extension_prefs_(ExtensionPrefs::Get(context)),
284 extension_registry_observer_(this),
285 weak_ptr_factory_(this) {
286 DCHECK(extension_prefs_);
288 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
289 content::Source<content::BrowserContext>(context_));
291 extension_registry_observer_.Add(ExtensionRegistry::Get(context_));
294 ExtensionStorageMonitor::~ExtensionStorageMonitor() {}
296 void ExtensionStorageMonitor::Observe(
297 int type,
298 const content::NotificationSource& source,
299 const content::NotificationDetails& details) {
300 switch (type) {
301 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
302 StopMonitoringAll();
303 break;
305 default:
306 NOTREACHED();
310 void ExtensionStorageMonitor::OnExtensionLoaded(
311 content::BrowserContext* browser_context,
312 const Extension* extension) {
313 StartMonitoringStorage(extension);
316 void ExtensionStorageMonitor::OnExtensionUnloaded(
317 content::BrowserContext* browser_context,
318 const Extension* extension,
319 UnloadedExtensionInfo::Reason reason) {
320 StopMonitoringStorage(extension->id());
323 void ExtensionStorageMonitor::OnExtensionWillBeInstalled(
324 content::BrowserContext* browser_context,
325 const Extension* extension,
326 bool is_update,
327 bool from_ephemeral,
328 const std::string& old_name) {
329 // If an ephemeral app was promoted to a regular installed app, we may need to
330 // increase its next threshold.
331 if (!from_ephemeral || !ShouldMonitorStorageFor(extension))
332 return;
334 if (!enable_for_all_extensions_) {
335 // If monitoring is not enabled for installed extensions, just stop
336 // monitoring.
337 SetNextStorageThreshold(extension->id(), 0);
338 StopMonitoringStorage(extension->id());
339 return;
342 int64 next_threshold = GetNextStorageThresholdFromPrefs(extension->id());
343 if (next_threshold <= initial_extension_threshold_) {
344 // Clear the next threshold in the prefs. This effectively raises it to
345 // |initial_extension_threshold_|. If the current threshold is already
346 // higher than this, leave it as is.
347 SetNextStorageThreshold(extension->id(), 0);
349 if (storage_observer_.get()) {
350 BrowserThread::PostTask(
351 BrowserThread::IO,
352 FROM_HERE,
353 base::Bind(&StorageEventObserver::UpdateThresholdForExtension,
354 storage_observer_,
355 extension->id(),
356 initial_extension_threshold_));
361 void ExtensionStorageMonitor::OnExtensionUninstalled(
362 content::BrowserContext* browser_context,
363 const Extension* extension,
364 extensions::UninstallReason reason) {
365 RemoveNotificationForExtension(extension->id());
368 void ExtensionStorageMonitor::ExtensionUninstallAccepted() {
369 DCHECK(!uninstall_extension_id_.empty());
371 const Extension* extension = GetExtensionById(context_,
372 uninstall_extension_id_);
373 uninstall_extension_id_.clear();
374 if (!extension)
375 return;
377 ExtensionService* service =
378 ExtensionSystem::Get(context_)->extension_service();
379 DCHECK(service);
380 service->UninstallExtension(
381 extension->id(),
382 extensions::UNINSTALL_REASON_STORAGE_THRESHOLD_EXCEEDED,
383 base::Bind(&base::DoNothing),
384 NULL);
387 void ExtensionStorageMonitor::ExtensionUninstallCanceled() {
388 uninstall_extension_id_.clear();
391 std::string ExtensionStorageMonitor::GetNotificationId(
392 const std::string& extension_id) {
393 std::vector<std::string> placeholders;
394 placeholders.push_back(context_->GetPath().BaseName().MaybeAsASCII());
395 placeholders.push_back(extension_id);
397 return ReplaceStringPlaceholders(kNotificationIdFormat, placeholders, NULL);
400 void ExtensionStorageMonitor::OnStorageThresholdExceeded(
401 const std::string& extension_id,
402 int64 next_threshold,
403 int64 current_usage) {
404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
406 const Extension* extension = GetExtensionById(context_, extension_id);
407 if (!extension)
408 return;
410 if (GetNextStorageThreshold(extension->id()) < next_threshold)
411 SetNextStorageThreshold(extension->id(), next_threshold);
413 const int kIconSize = message_center::kNotificationIconSize;
414 ExtensionResource resource = IconsInfo::GetIconResource(
415 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
416 ImageLoader::Get(context_)->LoadImageAsync(
417 extension, resource, gfx::Size(kIconSize, kIconSize),
418 base::Bind(&ExtensionStorageMonitor::OnImageLoaded,
419 weak_ptr_factory_.GetWeakPtr(),
420 extension_id,
421 current_usage));
424 void ExtensionStorageMonitor::OnImageLoaded(
425 const std::string& extension_id,
426 int64 current_usage,
427 const gfx::Image& image) {
428 const Extension* extension = GetExtensionById(context_, extension_id);
429 if (!extension)
430 return;
432 // Remove any existing notifications to force a new notification to pop up.
433 std::string notification_id(GetNotificationId(extension_id));
434 message_center::MessageCenter::Get()->RemoveNotification(
435 notification_id, false);
437 message_center::RichNotificationData notification_data;
438 notification_data.buttons.push_back(message_center::ButtonInfo(
439 l10n_util::GetStringUTF16(extension->is_app() ?
440 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_APP :
441 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_DISMISS_EXTENSION)));
442 notification_data.buttons.push_back(message_center::ButtonInfo(
443 l10n_util::GetStringUTF16(extension->is_app() ?
444 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_APP :
445 IDS_EXTENSION_STORAGE_MONITOR_BUTTON_UNINSTALL_EXTENSION)));
447 gfx::Image notification_image(image);
448 if (notification_image.IsEmpty()) {
449 notification_image =
450 extension->is_app() ? gfx::Image(util::GetDefaultAppIcon())
451 : gfx::Image(util::GetDefaultExtensionIcon());
454 scoped_ptr<message_center::Notification> notification;
455 notification.reset(new message_center::Notification(
456 message_center::NOTIFICATION_TYPE_SIMPLE,
457 notification_id,
458 l10n_util::GetStringUTF16(IDS_EXTENSION_STORAGE_MONITOR_TITLE),
459 l10n_util::GetStringFUTF16(
460 IDS_EXTENSION_STORAGE_MONITOR_TEXT,
461 base::UTF8ToUTF16(extension->name()),
462 base::IntToString16(current_usage / kMBytes)),
463 notification_image,
464 base::string16() /* display source */,
465 message_center::NotifierId(
466 message_center::NotifierId::SYSTEM_COMPONENT, kSystemNotifierId),
467 notification_data,
468 new message_center::HandleNotificationButtonClickDelegate(base::Bind(
469 &ExtensionStorageMonitor::OnNotificationButtonClick,
470 weak_ptr_factory_.GetWeakPtr(),
471 extension_id))));
472 notification->SetSystemPriority();
473 message_center::MessageCenter::Get()->AddNotification(notification.Pass());
475 notified_extension_ids_.insert(extension_id);
478 void ExtensionStorageMonitor::OnNotificationButtonClick(
479 const std::string& extension_id, int button_index) {
480 switch (button_index) {
481 case BUTTON_DISABLE_NOTIFICATION: {
482 DisableStorageMonitoring(extension_id);
483 break;
485 case BUTTON_UNINSTALL: {
486 ShowUninstallPrompt(extension_id);
487 break;
489 default:
490 NOTREACHED();
494 void ExtensionStorageMonitor::DisableStorageMonitoring(
495 const std::string& extension_id) {
496 scoped_refptr<const Extension> extension =
497 ExtensionRegistry::Get(context_)->enabled_extensions().GetByID(
498 extension_id);
499 if (!extension.get() || !ShouldGatherMetricsFor(extension.get()))
500 StopMonitoringStorage(extension_id);
502 SetStorageNotificationEnabled(extension_id, false);
504 message_center::MessageCenter::Get()->RemoveNotification(
505 GetNotificationId(extension_id), false);
508 void ExtensionStorageMonitor::StartMonitoringStorage(
509 const Extension* extension) {
510 if (!ShouldMonitorStorageFor(extension))
511 return;
513 // First apply this feature only to experimental ephemeral apps. If it works
514 // well, roll it out to all extensions and apps.
515 bool should_enforce =
516 (enable_for_all_extensions_ ||
517 extension_prefs_->IsEphemeralApp(extension->id())) &&
518 IsStorageNotificationEnabled(extension->id());
520 bool for_metrics = ShouldGatherMetricsFor(extension);
522 if (!should_enforce && !for_metrics)
523 return; // Don't track this extension.
525 // Lazily create the storage monitor proxy on the IO thread.
526 if (!storage_observer_.get()) {
527 storage_observer_ =
528 new StorageEventObserver(weak_ptr_factory_.GetWeakPtr());
531 GURL site_url = util::GetSiteForExtensionId(extension->id(), context_);
532 content::StoragePartition* storage_partition =
533 content::BrowserContext::GetStoragePartitionForSite(context_, site_url);
534 DCHECK(storage_partition);
535 scoped_refptr<storage::QuotaManager> quota_manager(
536 storage_partition->GetQuotaManager());
538 GURL storage_origin(site_url.GetOrigin());
539 if (extension->is_hosted_app())
540 storage_origin = AppLaunchInfo::GetLaunchWebURL(extension).GetOrigin();
542 // Don't give a threshold if we're not enforcing.
543 int next_threshold =
544 should_enforce ? GetNextStorageThreshold(extension->id()) : -1;
546 BrowserThread::PostTask(
547 BrowserThread::IO,
548 FROM_HERE,
549 base::Bind(&StorageEventObserver::StartObservingForExtension,
550 storage_observer_,
551 quota_manager,
552 extension->id(),
553 storage_origin,
554 next_threshold,
555 observer_rate_,
556 for_metrics));
559 void ExtensionStorageMonitor::StopMonitoringStorage(
560 const std::string& extension_id) {
561 if (!storage_observer_.get())
562 return;
564 BrowserThread::PostTask(
565 BrowserThread::IO,
566 FROM_HERE,
567 base::Bind(&StorageEventObserver::StopObservingForExtension,
568 storage_observer_,
569 extension_id));
572 void ExtensionStorageMonitor::StopMonitoringAll() {
573 extension_registry_observer_.RemoveAll();
575 RemoveAllNotifications();
577 if (!storage_observer_.get())
578 return;
580 BrowserThread::PostTask(
581 BrowserThread::IO,
582 FROM_HERE,
583 base::Bind(&StorageEventObserver::StopObserving, storage_observer_));
584 storage_observer_ = NULL;
587 void ExtensionStorageMonitor::RemoveNotificationForExtension(
588 const std::string& extension_id) {
589 std::set<std::string>::iterator ext_id =
590 notified_extension_ids_.find(extension_id);
591 if (ext_id == notified_extension_ids_.end())
592 return;
594 notified_extension_ids_.erase(ext_id);
595 message_center::MessageCenter::Get()->RemoveNotification(
596 GetNotificationId(extension_id), false);
599 void ExtensionStorageMonitor::RemoveAllNotifications() {
600 if (notified_extension_ids_.empty())
601 return;
603 message_center::MessageCenter* center = message_center::MessageCenter::Get();
604 DCHECK(center);
605 for (std::set<std::string>::iterator it = notified_extension_ids_.begin();
606 it != notified_extension_ids_.end(); ++it) {
607 center->RemoveNotification(GetNotificationId(*it), false);
609 notified_extension_ids_.clear();
612 void ExtensionStorageMonitor::ShowUninstallPrompt(
613 const std::string& extension_id) {
614 const Extension* extension = GetExtensionById(context_, extension_id);
615 if (!extension)
616 return;
618 if (!uninstall_dialog_.get()) {
619 uninstall_dialog_.reset(ExtensionUninstallDialog::Create(
620 Profile::FromBrowserContext(context_), NULL, this));
623 uninstall_extension_id_ = extension->id();
624 uninstall_dialog_->ConfirmUninstall(extension);
627 int64 ExtensionStorageMonitor::GetNextStorageThreshold(
628 const std::string& extension_id) const {
629 int next_threshold = GetNextStorageThresholdFromPrefs(extension_id);
630 if (next_threshold == 0) {
631 // The next threshold is written to the prefs after the initial threshold is
632 // exceeded.
633 next_threshold = extension_prefs_->IsEphemeralApp(extension_id)
634 ? initial_ephemeral_threshold_
635 : initial_extension_threshold_;
637 return next_threshold;
640 void ExtensionStorageMonitor::SetNextStorageThreshold(
641 const std::string& extension_id,
642 int64 next_threshold) {
643 extension_prefs_->UpdateExtensionPref(
644 extension_id,
645 kPrefNextStorageThreshold,
646 next_threshold > 0
647 ? new base::StringValue(base::Int64ToString(next_threshold))
648 : NULL);
651 int64 ExtensionStorageMonitor::GetNextStorageThresholdFromPrefs(
652 const std::string& extension_id) const {
653 std::string next_threshold_str;
654 if (extension_prefs_->ReadPrefAsString(
655 extension_id, kPrefNextStorageThreshold, &next_threshold_str)) {
656 int64 next_threshold;
657 if (base::StringToInt64(next_threshold_str, &next_threshold))
658 return next_threshold;
661 // A return value of zero indicates that the initial threshold has not yet
662 // been reached.
663 return 0;
666 bool ExtensionStorageMonitor::IsStorageNotificationEnabled(
667 const std::string& extension_id) const {
668 bool disable_notifications;
669 if (extension_prefs_->ReadPrefAsBoolean(extension_id,
670 kPrefDisableStorageNotifications,
671 &disable_notifications)) {
672 return !disable_notifications;
675 return true;
678 void ExtensionStorageMonitor::SetStorageNotificationEnabled(
679 const std::string& extension_id,
680 bool enable_notifications) {
681 extension_prefs_->UpdateExtensionPref(
682 extension_id,
683 kPrefDisableStorageNotifications,
684 enable_notifications ? NULL : new base::FundamentalValue(true));
687 } // namespace extensions