summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-05 05:13:15 +0000
committerasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-05 05:13:15 +0000
commitaf9db5fc79084fe986cf51ccc8b3475a5a4ef76f (patch)
tree119a36be14a106056ddf2e03e4c68c8baddd8977 /chrome
parent38476438089c4ddba8b698a29e1ea57cea61f90c (diff)
downloadchromium_src-af9db5fc79084fe986cf51ccc8b3475a5a4ef76f.zip
chromium_src-af9db5fc79084fe986cf51ccc8b3475a5a4ef76f.tar.gz
chromium_src-af9db5fc79084fe986cf51ccc8b3475a5a4ef76f.tar.bz2
Persist App Notifications to disk
BUG=98138 TEST=Install a packaged app with the experimental permission, and have it call chrome.experimental.app.notify({title:"foo", bodyText:"bar"}) from one of its pages. That should make a notification appear on its icon on the NTP. After restarting chrome, that same notification should still be there. Review URL: http://codereview.chromium.org/8038040 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@104066 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/app_notification.cc56
-rw-r--r--chrome/browser/extensions/app_notification.h11
-rw-r--r--chrome/browser/extensions/app_notification_manager.cc105
-rw-r--r--chrome/browser/extensions/app_notification_manager.h36
-rw-r--r--chrome/browser/extensions/app_notification_manager_unittest.cc123
-rw-r--r--chrome/browser/extensions/app_notification_storage.cc211
-rw-r--r--chrome/browser/extensions/app_notification_storage.h43
-rw-r--r--chrome/browser/extensions/app_notification_storage_unittest.cc123
-rw-r--r--chrome/browser/extensions/app_notification_test_util.cc55
-rw-r--r--chrome/browser/extensions/app_notification_test_util.h37
-rw-r--r--chrome/browser/extensions/extension_app_api.cc1
-rw-r--r--chrome/browser/extensions/extension_service.cc4
-rw-r--r--chrome/browser/extensions/extension_service.h6
-rw-r--r--chrome/browser/extensions/file_reader_unittest.cc9
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests.gypi5
-rw-r--r--chrome/common/extensions/extension.h3
-rw-r--r--chrome/common/extensions/extension_resource_unittest.cc7
-rw-r--r--chrome/common/extensions/extension_test_util.cc20
-rw-r--r--chrome/common/extensions/extension_test_util.h18
20 files changed, 854 insertions, 21 deletions
diff --git a/chrome/browser/extensions/app_notification.cc b/chrome/browser/extensions/app_notification.cc
index b54568b..f8e60e3 100644
--- a/chrome/browser/extensions/app_notification.cc
+++ b/chrome/browser/extensions/app_notification.cc
@@ -4,8 +4,64 @@
#include "chrome/browser/extensions/app_notification.h"
+#include "base/memory/scoped_ptr.h"
+
AppNotification::AppNotification(const std::string& title,
const std::string& body)
: title_(title), body_(body) {}
AppNotification::~AppNotification() {}
+
+namespace {
+
+const char* kTitleKey = "title";
+const char* kBodyKey = "body";
+const char* kLinkUrlKey = "link_url";
+const char* kLinkTextKey = "link_text";
+
+} // namespace
+
+void AppNotification::ToDictionaryValue(DictionaryValue* result) {
+ CHECK(result);
+ if (!title_.empty())
+ result->SetString(kTitleKey, title_);
+ if (!body_.empty())
+ result->SetString(kBodyKey, body_);
+ if (!link_url_.is_empty()) {
+ result->SetString(kLinkUrlKey, link_url_.possibly_invalid_spec());
+ result->SetString(kLinkTextKey, link_text_);
+ }
+}
+
+// static
+AppNotification* AppNotification::FromDictionaryValue(
+ const DictionaryValue& value) {
+
+ scoped_ptr<AppNotification> result(new AppNotification("", ""));
+
+ if (value.HasKey(kTitleKey) && !value.GetString(kTitleKey, &result->title_))
+ return NULL;
+ if (value.HasKey(kBodyKey) && !value.GetString(kBodyKey, &result->body_))
+ return NULL;
+ if (value.HasKey(kLinkUrlKey)) {
+ if (!value.HasKey(kLinkTextKey))
+ return NULL;
+ std::string url;
+ if (!value.GetString(kLinkUrlKey, &url) ||
+ !value.GetString(kLinkTextKey, &result->link_text_))
+ return NULL;
+ GURL gurl(url);
+ if (!gurl.is_valid())
+ return NULL;
+ result->set_link_url(gurl);
+ }
+
+ return result.release();
+}
+
+bool AppNotification::Equals(const AppNotification& other) const {
+ return (title_ == other.title_ &&
+ body_ == other.body_ &&
+ link_url_ == other.link_url_ &&
+ link_text_ == other.link_text_);
+}
diff --git a/chrome/browser/extensions/app_notification.h b/chrome/browser/extensions/app_notification.h
index 95cf1f4..8fd7743 100644
--- a/chrome/browser/extensions/app_notification.h
+++ b/chrome/browser/extensions/app_notification.h
@@ -10,6 +10,7 @@
#include <vector>
#include "base/memory/linked_ptr.h"
+#include "base/values.h"
#include "googleurl/src/gurl.h"
// This class is used to represent a notification for an installed app, to be
@@ -29,7 +30,17 @@ class AppNotification {
const GURL& link_url() const { return link_url_; }
const std::string& link_text() const { return link_text_; }
+ // Useful methods for serialization.
+ void ToDictionaryValue(DictionaryValue* result);
+ static AppNotification* FromDictionaryValue(const DictionaryValue& value);
+
+ // Return whether |other| is equal to this object. Useful for tests.
+ bool Equals(const AppNotification& other) const;
+
private:
+ // If you add to the list of data members, make sure to add appropriate checks
+ // to the Equals and {To,From}DictionaryValue methods, keeping in mind
+ // backwards compatibility.
std::string title_;
std::string body_;
GURL link_url_;
diff --git a/chrome/browser/extensions/app_notification_manager.cc b/chrome/browser/extensions/app_notification_manager.cc
index 798ba44..1b6d1d6 100644
--- a/chrome/browser/extensions/app_notification_manager.cc
+++ b/chrome/browser/extensions/app_notification_manager.cc
@@ -4,18 +4,47 @@
#include "chrome/browser/extensions/app_notification_manager.h"
+#include "base/bind.h"
+#include "base/file_path.h"
#include "base/stl_util.h"
+#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
+#include "content/browser/browser_thread.h"
#include "content/common/notification_service.h"
-AppNotificationManager::AppNotificationManager() {
+AppNotificationManager::AppNotificationManager(Profile* profile)
+ : profile_(profile) {
registrar_.Add(this,
chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
- NotificationService::AllSources());
+ Source<Profile>(profile_));
}
-AppNotificationManager::~AppNotificationManager() {}
+namespace {
+
+void DeleteStorageOnFileThread(AppNotificationStorage* storage) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ delete storage;
+}
+
+} // namespace
+
+AppNotificationManager::~AppNotificationManager() {
+ // Post a task to delete our storage on the file thread.
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&DeleteStorageOnFileThread, storage_.release()));
+}
+
+void AppNotificationManager::Init() {
+ FilePath storage_path = profile_->GetPath().AppendASCII("App Notifications");
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &AppNotificationManager::LoadOnFileThread, this, storage_path));
+}
void AppNotificationManager::Add(const std::string& extension_id,
AppNotification* item) {
@@ -27,6 +56,15 @@ void AppNotificationManager::Add(const std::string& extension_id,
CHECK(found != notifications_.end());
AppNotificationList& list = (*found).second;
list.push_back(linked_ptr<AppNotification>(item));
+
+ if (!storage_.get())
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &AppNotificationManager::SaveOnFileThread, this, extension_id, list));
}
const AppNotificationList* AppNotificationManager::GetAll(
@@ -49,6 +87,14 @@ void AppNotificationManager::ClearAll(const std::string& extension_id) {
NotificationMap::iterator found = notifications_.find(extension_id);
if (found != notifications_.end())
notifications_.erase(found);
+
+ if (!storage_.get())
+ return;
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &AppNotificationManager::DeleteOnFileThread, this, extension_id));
}
void AppNotificationManager::Observe(int type,
@@ -59,3 +105,56 @@ void AppNotificationManager::Observe(int type,
Details<UninstalledExtensionInfo>(details)->extension_id;
ClearAll(id);
}
+
+void AppNotificationManager::LoadOnFileThread(const FilePath& storage_path) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ storage_.reset(AppNotificationStorage::Create(storage_path));
+ if (!storage_.get())
+ return;
+ NotificationMap result;
+ std::set<std::string> ids;
+ if (!storage_->GetExtensionIds(&ids))
+ return;
+ std::set<std::string>::const_iterator i;
+ for (i = ids.begin(); i != ids.end(); ++i) {
+ const std::string& id = *i;
+ AppNotificationList& list = result[id];
+ if (!storage_->Get(id, &list))
+ result.erase(id);
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&AppNotificationManager::HandleLoadResults, this, result));
+}
+
+void AppNotificationManager::HandleLoadResults(const NotificationMap& map) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ NotificationMap::const_iterator i;
+ for (i = map.begin(); i != map.end(); ++i) {
+ const std::string& id = i->first;
+ const AppNotificationList& list = i->second;
+ if (list.empty())
+ continue;
+ notifications_[id].insert(notifications_[id].begin(),
+ list.begin(),
+ list.end());
+ NotificationService::current()->Notify(
+ chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
+ Source<Profile>(profile_),
+ Details<const std::string>(&id));
+ }
+}
+
+void AppNotificationManager::SaveOnFileThread(const std::string& extension_id,
+ const AppNotificationList& list) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ storage_->Set(extension_id, list);
+}
+
+void AppNotificationManager::DeleteOnFileThread(
+ const std::string& extension_id) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ storage_->Delete(extension_id);
+}
diff --git a/chrome/browser/extensions/app_notification_manager.h b/chrome/browser/extensions/app_notification_manager.h
index 4249473..8148a08 100644
--- a/chrome/browser/extensions/app_notification_manager.h
+++ b/chrome/browser/extensions/app_notification_manager.h
@@ -9,17 +9,24 @@
#include <map>
#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/app_notification.h"
-#include "content/common/notification_details.h"
+#include "chrome/browser/extensions/app_notification_storage.h"
#include "content/common/notification_observer.h"
#include "content/common/notification_registrar.h"
-#include "content/common/notification_source.h"
+
+class Profile;
// This class keeps track of notifications for installed apps.
-class AppNotificationManager : public NotificationObserver {
+class AppNotificationManager
+ : public base::RefCountedThreadSafe<AppNotificationManager>,
+ public NotificationObserver {
public:
- AppNotificationManager();
- virtual ~AppNotificationManager();
+ explicit AppNotificationManager(Profile* profile);
+
+ // Starts up the process of reading from persistent storage.
+ void Init();
// Takes ownership of |notification|.
void Add(const std::string& extension_id, AppNotification* item);
@@ -39,12 +46,31 @@ class AppNotificationManager : public NotificationObserver {
const NotificationDetails& details) OVERRIDE;
private:
+ friend class base::RefCountedThreadSafe<AppNotificationManager>;
+
// Maps extension id to a list of notifications for that extension.
typedef std::map<std::string, AppNotificationList> NotificationMap;
+ virtual ~AppNotificationManager();
+
+ // Starts loading storage_ using |storage_path|.
+ void LoadOnFileThread(const FilePath& storage_path);
+
+ // Called on the UI thread to handle the loaded results from storage_.
+ void HandleLoadResults(const NotificationMap& map);
+
+ void SaveOnFileThread(const std::string& extension_id,
+ const AppNotificationList& list);
+
+ void DeleteOnFileThread(const std::string& extension_id);
+
+ Profile* profile_;
NotificationRegistrar registrar_;
NotificationMap notifications_;
+ // This should only be used on the FILE thread.
+ scoped_ptr<AppNotificationStorage> storage_;
+
DISALLOW_COPY_AND_ASSIGN(AppNotificationManager);
};
diff --git a/chrome/browser/extensions/app_notification_manager_unittest.cc b/chrome/browser/extensions/app_notification_manager_unittest.cc
new file mode 100644
index 0000000..99c513a
--- /dev/null
+++ b/chrome/browser/extensions/app_notification_manager_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2011 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 "base/bind.h"
+#include "chrome/browser/extensions/app_notification_manager.h"
+#include "chrome/browser/extensions/app_notification_test_util.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_test_util.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/common/notification_service.h"
+#include "content/common/notification_source.h"
+#include "content/common/notification_details.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace util = app_notification_test_util;
+
+class AppNotificationManagerTest : public testing::Test {
+ public:
+ AppNotificationManagerTest()
+ : ui_thread_(BrowserThread::UI, &ui_loop_),
+ file_thread_(BrowserThread::FILE) {}
+
+ ~AppNotificationManagerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ file_thread_.Start();
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ profile_.reset(new TestingProfile(temp_dir_.path()));
+ InitializeManager();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ mgr_ = NULL;
+ // Let the manager's shutdown of storage on the file thread complete.
+ WaitForFileThread();
+ }
+
+ protected:
+ void InitializeManager() {
+ if (mgr_.get())
+ WaitForFileThread();
+ mgr_ = new AppNotificationManager(profile_.get());
+ mgr_->Init();
+ WaitForFileThread();
+ }
+
+ static void PostQuitToUIThread() {
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ new MessageLoop::QuitTask());
+ }
+
+ static void WaitForFileThread() {
+ BrowserThread::PostTask(BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&PostQuitToUIThread));
+ MessageLoop::current()->Run();
+ }
+
+ MessageLoop ui_loop_;
+ BrowserThread ui_thread_;
+ BrowserThread file_thread_;
+ ScopedTempDir temp_dir_;
+ scoped_ptr<TestingProfile> profile_;
+ scoped_refptr<AppNotificationManager> mgr_;
+};
+
+TEST_F(AppNotificationManagerTest, Simple) {
+ std::string id = extension_test_util::MakeId("whatever");
+ AppNotificationList list;
+ util::AddNotifications(&list, 2, "foo");
+ util::AddCopiesFromList(mgr_.get(), id, list);
+
+ // Cause |mgr_| to be recreated, re-reading from its storage.
+ InitializeManager();
+
+ const AppNotification* tmp = mgr_->GetLast(id);
+ EXPECT_TRUE(tmp && list[1]->Equals(*tmp));
+ const AppNotificationList* tmp_list = mgr_->GetAll(id);
+ ASSERT_TRUE(tmp_list != NULL);
+ util::ExpectListsEqual(list, *tmp_list);
+ mgr_->ClearAll(id);
+ EXPECT_EQ(NULL, mgr_->GetLast(id));
+ EXPECT_EQ(NULL, mgr_->GetAll(id));
+}
+
+// Test that AppNotificationManager correctly lists to EXTENSION_UNINSTALLED
+// notifications and removes associated data when that happens.
+TEST_F(AppNotificationManagerTest, ExtensionUninstall) {
+ // Add some items from two test extension ids.
+ std::string id1 = extension_test_util::MakeId("id1");
+ std::string id2 = extension_test_util::MakeId("id2");
+ AppNotificationList list1;
+ AppNotificationList list2;
+ util::AddNotifications(&list1, 5, "foo1");
+ util::AddNotifications(&list2, 3, "foo2");
+ util::AddCopiesFromList(mgr_.get(), id1, list1);
+ util::AddCopiesFromList(mgr_.get(), id2, list2);
+ util::ExpectListsEqual(list1, *mgr_->GetAll(id1));
+ util::ExpectListsEqual(list2, *mgr_->GetAll(id2));
+
+ // Send the uninstall notification for extension id1.
+ DictionaryValue dict;
+ dict.SetString(extension_manifest_keys::kName, "Test Extension");
+ dict.SetString(extension_manifest_keys::kVersion, "0.1");
+ std::string error;
+ scoped_refptr<Extension> extension = Extension::CreateWithId(
+ temp_dir_.path().AppendASCII("dummy"), Extension::INTERNAL,
+ dict, 0, id1, &error);
+ UninstalledExtensionInfo info(*extension.get());
+ NotificationService::current()->Notify(
+ chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
+ Source<Profile>(profile_.get()),
+ Details<UninstalledExtensionInfo>(&info));
+
+ // The id1 items should be gone but the id2 items should still be there.
+ EXPECT_EQ(NULL, mgr_->GetLast(id1));
+ EXPECT_EQ(NULL, mgr_->GetAll(id1));
+ util::ExpectListsEqual(list2, *mgr_->GetAll(id2));
+}
diff --git a/chrome/browser/extensions/app_notification_storage.cc b/chrome/browser/extensions/app_notification_storage.cc
new file mode 100644
index 0000000..57d7612
--- /dev/null
+++ b/chrome/browser/extensions/app_notification_storage.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2011 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/app_notification_storage.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/location.h"
+#include "base/sys_string_conversions.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/common/extensions/extension.h"
+#include "content/browser/browser_thread.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+using base::JSONReader;
+using base::JSONWriter;
+
+// A concrete implementation of the AppNotificationStorage interface, using
+// LevelDb for backing storage.
+class LevelDbAppNotificationStorage : public AppNotificationStorage {
+ public:
+ explicit LevelDbAppNotificationStorage(const FilePath& path);
+ virtual ~LevelDbAppNotificationStorage();
+
+ // Must be called exactly once before any other operations. This will return
+ // false if any condition would prevent further operations from being
+ // successful (eg unable to create files at |path_|).
+ bool Initialize();
+
+ // Implementing the AppNotificationStorage interface.
+ virtual bool GetExtensionIds(std::set<std::string>* result) OVERRIDE;
+ virtual bool Get(const std::string& extension_id,
+ AppNotificationList* result) OVERRIDE;
+ virtual bool Set(const std::string& extension_id,
+ const AppNotificationList& list) OVERRIDE;
+ virtual bool Delete(const std::string& extension_id) OVERRIDE;
+
+ private:
+ // The path where the database will reside.
+ FilePath path_;
+
+ // This should be used for all read operations on the db.
+ leveldb::ReadOptions read_options_;
+
+ // The leveldb database object - will be NULL until Initialize is called.
+ scoped_ptr<leveldb::DB> db_;
+
+ DISALLOW_COPY_AND_ASSIGN(LevelDbAppNotificationStorage);
+};
+
+// static
+AppNotificationStorage* AppNotificationStorage::Create(
+ const FilePath& path) {
+ scoped_ptr<LevelDbAppNotificationStorage> result(
+ new LevelDbAppNotificationStorage(path));
+ if (!result->Initialize())
+ return NULL;
+ return result.release();
+}
+
+AppNotificationStorage::~AppNotificationStorage() {}
+
+namespace {
+
+void AppNotificationListToJSON(const AppNotificationList& list,
+ std::string* result) {
+ ListValue list_value;
+ AppNotificationList::const_iterator i;
+ for (i = list.begin(); i != list.end(); ++i) {
+ DictionaryValue* dictionary = new DictionaryValue();
+ (*i)->ToDictionaryValue(dictionary);
+ list_value.Append(dictionary);
+ }
+ JSONWriter::Write(&list_value, false /* pretty_print */, result);
+}
+
+bool JSONToAppNotificationList(const std::string& json,
+ AppNotificationList* list) {
+ CHECK(list);
+ scoped_ptr<Value> value(JSONReader::Read(json,
+ false /* allow_trailing_comma */));
+ if (!value.get() || value->GetType() != Value::TYPE_LIST)
+ return false;
+
+ ListValue* list_value = static_cast<ListValue*>(value.get());
+ for (size_t i = 0; i < list_value->GetSize(); i++) {
+ Value* item = NULL;
+ if (!list_value->Get(i, &item) || !item ||
+ item->GetType() != Value::TYPE_DICTIONARY)
+ return false;
+ DictionaryValue* dictionary = static_cast<DictionaryValue*>(item);
+ AppNotification* notification =
+ AppNotification::FromDictionaryValue(*dictionary);
+ if (!notification)
+ return false;
+ list->push_back(linked_ptr<AppNotification>(notification));
+ }
+ return true;
+}
+
+void LogLevelDbError(tracked_objects::Location location,
+ const leveldb::Status& status) {
+ LOG(ERROR) << "AppNotificationStorage database error at "
+ << location.ToString() << " status:" << status.ToString();
+}
+
+} // namespace
+
+
+LevelDbAppNotificationStorage::LevelDbAppNotificationStorage(
+ const FilePath& path) : path_(path) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ read_options_.verify_checksums = true;
+}
+
+LevelDbAppNotificationStorage::~LevelDbAppNotificationStorage() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+}
+
+bool LevelDbAppNotificationStorage::Initialize() {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ CHECK(!db_.get());
+ CHECK(!path_.empty());
+
+#if defined(OS_POSIX)
+ std::string os_path = path_.value();
+#elif defined(OS_WIN)
+ std::string os_path = base::SysWideToUTF8(path_.value());
+#endif
+
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::DB* db = NULL;
+ leveldb::Status status = leveldb::DB::Open(options, os_path, &db);
+ if (!status.ok()) {
+ LogLevelDbError(FROM_HERE, status);
+ return false;
+ }
+ db_.reset(db);
+ return true;
+}
+
+bool LevelDbAppNotificationStorage::GetExtensionIds(
+ std::set<std::string>* result) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ CHECK(db_.get());
+ CHECK(result);
+
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options_));
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
+ std::string key = iter->key().ToString();
+ if (Extension::IdIsValid(key))
+ result->insert(key);
+ }
+
+ return true;
+}
+
+bool LevelDbAppNotificationStorage::Get(const std::string& extension_id,
+ AppNotificationList* result) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ CHECK(db_.get());
+ CHECK(result);
+
+ std::string json;
+ leveldb::Status status = db_->Get(read_options_, extension_id, &json);
+ if (status.IsNotFound()) {
+ return true;
+ } else if (!status.ok()) {
+ LogLevelDbError(FROM_HERE, status);
+ return false;
+ }
+
+ return JSONToAppNotificationList(json, result);
+}
+
+bool LevelDbAppNotificationStorage::Set(const std::string& extension_id,
+ const AppNotificationList& list) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ CHECK(db_.get());
+
+ std::string json;
+ AppNotificationListToJSON(list, &json);
+ leveldb::Status status = db_->Put(leveldb::WriteOptions(),
+ extension_id,
+ json);
+ if (!status.ok()) {
+ LogLevelDbError(FROM_HERE, status);
+ return false;
+ }
+ return true;
+}
+
+bool LevelDbAppNotificationStorage::Delete(const std::string& extension_id) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ CHECK(db_.get());
+
+ // Leveldb does not consider it an error if the key to delete isn't present,
+ // so we don't bother checking that first.
+ leveldb::Status status = db_->Delete(leveldb::WriteOptions(), extension_id);
+
+ if (!status.ok()) {
+ LogLevelDbError(FROM_HERE, status);
+ return false;
+ }
+ return true;
+}
diff --git a/chrome/browser/extensions/app_notification_storage.h b/chrome/browser/extensions/app_notification_storage.h
new file mode 100644
index 0000000..2ca1e54
--- /dev/null
+++ b/chrome/browser/extensions/app_notification_storage.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_APP_NOTIFICATION_STORAGE_H__
+#define CHROME_BROWSER_EXTENSIONS_APP_NOTIFICATION_STORAGE_H__
+#pragma once
+
+#include <set>
+
+#include "chrome/browser/extensions/app_notification.h"
+
+class FilePath;
+
+// Represents storage for app notifications for a particular extension id.
+//
+// IMPORTANT NOTE: Instances of this class should only be used on the FILE
+// thread.
+class AppNotificationStorage {
+ public:
+ // Must be called on the FILE thread. The storage will be created at |path|.
+ static AppNotificationStorage* Create(const FilePath& path);
+
+ virtual ~AppNotificationStorage();
+
+ // Get the set of extension id's that have entries, putting them into
+ // |result|.
+ virtual bool GetExtensionIds(std::set<std::string>* result) = 0;
+
+ // Gets the list of stored notifications for extension_id. On success, writes
+ // results into |result|. On error, returns false.
+ virtual bool Get(const std::string& extension_id,
+ AppNotificationList* result) = 0;
+
+ // Writes the |list| for |extension_id| into storage.
+ virtual bool Set(const std::string& extension_id,
+ const AppNotificationList& list) = 0;
+
+ // Deletes all data for |extension_id|.
+ virtual bool Delete(const std::string& extension_id) = 0;
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_APP_NOTIFICATION_STORAGE_H__
diff --git a/chrome/browser/extensions/app_notification_storage_unittest.cc b/chrome/browser/extensions/app_notification_storage_unittest.cc
new file mode 100644
index 0000000..7ae2f155
--- /dev/null
+++ b/chrome/browser/extensions/app_notification_storage_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2011 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 "base/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/scoped_temp_dir.h"
+#include "base/stl_util.h"
+#include "chrome/browser/extensions/app_notification.h"
+#include "chrome/browser/extensions/app_notification_storage.h"
+#include "chrome/browser/extensions/app_notification_test_util.h"
+#include "chrome/common/extensions/extension_test_util.h"
+#include "content/browser/browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace util = app_notification_test_util;
+
+class AppNotificationStorageTest : public testing::Test {
+ public:
+ AppNotificationStorageTest() :
+ file_thread_(BrowserThread::FILE, &message_loop_) {}
+ virtual ~AppNotificationStorageTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+ storage_.reset(AppNotificationStorage::Create(dir_.path()));
+ ASSERT_TRUE(storage_.get() != NULL);
+ }
+
+ protected:
+ MessageLoop message_loop_;
+ BrowserThread file_thread_;
+ ScopedTempDir dir_;
+ scoped_ptr<AppNotificationStorage> storage_;
+};
+
+// Tests simple operations.
+TEST_F(AppNotificationStorageTest, Basics) {
+ std::set<std::string> tmp_ids;
+ AppNotificationList tmp_list;
+ std::string id1 = extension_test_util::MakeId("1");
+
+ // Try operations on non-existent keys.
+ EXPECT_TRUE(storage_->GetExtensionIds(&tmp_ids));
+ EXPECT_TRUE(tmp_ids.empty());
+ EXPECT_TRUE(storage_->Get(id1, &tmp_list));
+ EXPECT_TRUE(tmp_list.empty());
+ EXPECT_TRUE(storage_->Delete(id1));
+
+ // Add some items.
+ AppNotificationList list;
+ util::AddNotifications(&list, 2, "whatever");
+ EXPECT_TRUE(storage_->Set(id1, list));
+
+ // Try getting them back.
+ tmp_list.clear();
+ EXPECT_TRUE(storage_->Get(id1, &tmp_list));
+ util::ExpectListsEqual(list, tmp_list);
+ EXPECT_TRUE(storage_->GetExtensionIds(&tmp_ids));
+ EXPECT_EQ(tmp_ids.size(), 1U);
+ EXPECT_TRUE(ContainsKey(tmp_ids, id1));
+}
+
+// Tests with multiple extensions.
+TEST_F(AppNotificationStorageTest, MultipleExtensions) {
+ std::string id1 = extension_test_util::MakeId("1");
+ std::string id2 = extension_test_util::MakeId("2");
+
+ // Add items for id1.
+ AppNotificationList list1;
+ util::AddNotifications(&list1, 2, "one");
+ EXPECT_TRUE(storage_->Set(id1, list1));
+
+ // Add items for id2.
+ AppNotificationList list2;
+ util::AddNotifications(&list2, 3, "two");
+ EXPECT_TRUE(storage_->Set(id2, list2));
+
+ // Verify the items are present
+ std::set<std::string> tmp_ids;
+ EXPECT_TRUE(storage_->GetExtensionIds(&tmp_ids));
+ EXPECT_EQ(tmp_ids.size(), 2U);
+ EXPECT_TRUE(ContainsKey(tmp_ids, id1));
+ EXPECT_TRUE(ContainsKey(tmp_ids, id2));
+
+ AppNotificationList tmp_list;
+ EXPECT_TRUE(storage_->Get(id1, &tmp_list));
+ util::ExpectListsEqual(tmp_list, list1);
+ tmp_list.clear();
+ EXPECT_TRUE(storage_->Get(id2, &tmp_list));
+ util::ExpectListsEqual(tmp_list, list2);
+
+ // Delete the id1 items and check the results.
+ EXPECT_TRUE(storage_->Delete(id1));
+ tmp_ids.clear();
+ EXPECT_TRUE(storage_->GetExtensionIds(&tmp_ids));
+ EXPECT_EQ(tmp_ids.size(), 1U);
+ EXPECT_FALSE(ContainsKey(tmp_ids, id1));
+ EXPECT_TRUE(ContainsKey(tmp_ids, id2));
+ tmp_list.clear();
+ EXPECT_TRUE(storage_->Get(id1, &tmp_list));
+ EXPECT_TRUE(tmp_list.empty());
+ tmp_list.clear();
+ EXPECT_TRUE(storage_->Get(id2, &tmp_list));
+ util::ExpectListsEqual(tmp_list, list2);
+}
+
+// Tests using Set to replace existing entries.
+TEST_F(AppNotificationStorageTest, ReplaceExisting) {
+ std::string id = extension_test_util::MakeId("1");
+ AppNotificationList list1;
+ AppNotificationList list2;
+ util::AddNotifications(&list1, 5, "one");
+ util::AddNotifications(&list2, 7, "two");
+
+ // Put list1 in, then replace with list2 and verify we get list2 back.
+ EXPECT_TRUE(storage_->Set(id, list1));
+ EXPECT_TRUE(storage_->Set(id, list2));
+ AppNotificationList tmp_list;
+ EXPECT_TRUE(storage_->Get(id, &tmp_list));
+ util::ExpectListsEqual(list2, tmp_list);
+}
diff --git a/chrome/browser/extensions/app_notification_test_util.cc b/chrome/browser/extensions/app_notification_test_util.cc
new file mode 100644
index 0000000..a148e50
--- /dev/null
+++ b/chrome/browser/extensions/app_notification_test_util.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 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 "base/string_number_conversions.h"
+#include "chrome/browser/extensions/app_notification_manager.h"
+#include "chrome/browser/extensions/app_notification_test_util.h"
+#include "chrome/common/extensions/extension.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::IntToString;
+
+namespace app_notification_test_util {
+
+void ExpectListsEqual(const AppNotificationList& one,
+ const AppNotificationList& two) {
+ ASSERT_EQ(one.size(), two.size());
+ for (size_t i = 0; i < one.size(); i++) {
+ ASSERT_TRUE(one[i]->Equals(*two[i]));
+ }
+}
+
+void AddNotifications(AppNotificationList* list,
+ int count,
+ std::string prefix) {
+ for (int i = 0; i < count; i++) {
+ std::string title = prefix + "_title_" + IntToString(i);
+ std::string body = prefix + "_body_" + IntToString(i);
+ AppNotification* item = new AppNotification(title, body);
+ if (i % 2 == 0) {
+ item->set_link_url(GURL("http://www.example.com/" + prefix));
+ item->set_link_text(prefix + "_link_" + IntToString(i));
+ }
+ list->push_back(linked_ptr<AppNotification>(item));
+ }
+}
+
+AppNotification* CopyAppNotification(const AppNotification& source) {
+ AppNotification* copy = new AppNotification(source.title(), source.body());
+ copy->set_link_url(source.link_url());
+ copy->set_link_text(source.link_text());
+ return copy;
+}
+
+void AddCopiesFromList(AppNotificationManager* manager,
+ const std::string& extension_id,
+ const AppNotificationList& list) {
+ for (AppNotificationList::const_iterator i = list.begin();
+ i != list.end();
+ ++i) {
+ manager->Add(extension_id, CopyAppNotification(*(i->get())));
+ }
+}
+
+} // namespace app_notification_test_util
diff --git a/chrome/browser/extensions/app_notification_test_util.h b/chrome/browser/extensions/app_notification_test_util.h
new file mode 100644
index 0000000..0f38e5a
--- /dev/null
+++ b/chrome/browser/extensions/app_notification_test_util.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_APP_NOTIFICATION_TEST_UTIL_H_
+#define CHROME_BROWSER_EXTENSIONS_APP_NOTIFICATION_TEST_UTIL_H_
+#pragma once
+
+#include "chrome/browser/extensions/app_notification.h"
+
+class AppNotificationManager;
+
+namespace app_notification_test_util {
+
+// Does a deep equality check of two AppNotificationList's, adding a gtest
+// failure and logging at the first difference found.
+void ExpectListsEqual(const AppNotificationList& one,
+ const AppNotificationList& two);
+
+// Helper for inserting |count| dummy notifications with |prefix| in their
+// title and body into |list|.
+void AddNotifications(AppNotificationList* list,
+ int count,
+ std::string prefix);
+
+// Copy the contents of |source| into a new object.
+AppNotification* CopyAppNotification(const AppNotification& source);
+
+// Adds a copy of each item in |list| to |manager| using |extension_id| as the
+// extension id.
+void AddCopiesFromList(AppNotificationManager* manager,
+ const std::string& extension_id,
+ const AppNotificationList& list);
+
+} // namespace app_notification_test_util
+
+#endif // CHROME_BROWSER_EXTENSIONS_APP_NOTIFICATION_TEST_UTIL_H_
diff --git a/chrome/browser/extensions/extension_app_api.cc b/chrome/browser/extensions/extension_app_api.cc
index 95c2b6f..d89526c 100644
--- a/chrome/browser/extensions/extension_app_api.cc
+++ b/chrome/browser/extensions/extension_app_api.cc
@@ -5,6 +5,7 @@
#include "chrome/browser/extensions/extension_app_api.h"
#include "base/values.h"
+#include "chrome/browser/extensions/app_notification_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index fc0935b..1bc3aee 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -25,6 +25,7 @@
#include "base/version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_plugin_service_filter.h"
+#include "chrome/browser/extensions/app_notification_manager.h"
#include "chrome/browser/extensions/apps_promo.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_accessibility_api.h"
@@ -594,6 +595,7 @@ ExtensionService::ExtensionService(Profile* profile,
show_extensions_prompts_(true),
ready_(false),
toolbar_model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
+ app_notification_manager_(new AppNotificationManager(profile)),
permissions_manager_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
apps_promo_(profile->GetPrefs()),
event_routers_initialized_(false) {
@@ -635,6 +637,8 @@ ExtensionService::ExtensionService(Profile* profile,
new ExtensionServiceBackend(weak_ptr_factory_.GetWeakPtr(),
install_directory_);
+ app_notification_manager_->Init();
+
if (extensions_enabled_) {
ExternalExtensionProviderImpl::CreateExternalProviders(
this, profile_, &external_extension_providers_);
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index e12bd6f..2e1aa2b 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -21,7 +21,6 @@
#include "base/task.h"
#include "base/time.h"
#include "base/tuple.h"
-#include "chrome/browser/extensions/app_notification_manager.h"
#include "chrome/browser/extensions/apps_promo.h"
#include "chrome/browser/extensions/extension_icon_manager.h"
#include "chrome/browser/extensions/extension_menu_manager.h"
@@ -45,6 +44,7 @@
#include "content/common/notification_registrar.h"
#include "content/common/property_bag.h"
+class AppNotificationManager;
class CrxInstaller;
class ExtensionBookmarkEventRouter;
class ExtensionBrowserEventRouter;
@@ -454,7 +454,7 @@ class ExtensionService
ExtensionMenuManager* menu_manager() { return &menu_manager_; }
AppNotificationManager* app_notification_manager() {
- return &app_notification_manager_;
+ return app_notification_manager_.get();
}
ExtensionPermissionsManager* permissions_manager() {
@@ -741,7 +741,7 @@ class ExtensionService
ExtensionMenuManager menu_manager_;
// Keeps track of app notifications.
- AppNotificationManager app_notification_manager_;
+ scoped_refptr<AppNotificationManager> app_notification_manager_;
// Keeps track of extension permissions.
ExtensionPermissionsManager permissions_manager_;
diff --git a/chrome/browser/extensions/file_reader_unittest.cc b/chrome/browser/extensions/file_reader_unittest.cc
index 5b50588..08e5a08 100644
--- a/chrome/browser/extensions/file_reader_unittest.cc
+++ b/chrome/browser/extensions/file_reader_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
@@ -11,6 +11,7 @@
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/extension_test_util.h"
#include "content/browser/browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -52,8 +53,7 @@ class Receiver {
void RunBasicTest(const char* filename) {
FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
- std::string extension_id;
- Extension::GenerateId("test", &extension_id);
+ std::string extension_id = extension_test_util::MakeId("test");
ExtensionResource resource(extension_id, path,
FilePath().AppendASCII(filename));
path = path.AppendASCII(filename);
@@ -84,8 +84,7 @@ TEST_F(FileReaderTest, BiggerFile) {
TEST_F(FileReaderTest, NonExistantFile) {
FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
- std::string extension_id;
- Extension::GenerateId("test", &extension_id);
+ std::string extension_id = extension_test_util::MakeId("test");
ExtensionResource resource(extension_id, path, FilePath(
FILE_PATH_LITERAL("file_that_does_not_exist")));
path = path.AppendASCII("file_that_does_not_exist");
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index dd9a4c8..18419a8 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -899,6 +899,8 @@
'browser/extensions/app_notification.h',
'browser/extensions/app_notification_manager.cc',
'browser/extensions/app_notification_manager.h',
+ 'browser/extensions/app_notification_storage.cc',
+ 'browser/extensions/app_notification_storage.h',
'browser/extensions/apps_promo.cc',
'browser/extensions/apps_promo.h',
'browser/extensions/default_apps_trial.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 5df2a3c..09e34d9 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1249,6 +1249,9 @@
'browser/enumerate_modules_model_unittest_win.cc',
'browser/event_disposition.cc',
'browser/event_disposition.h',
+ 'browser/extensions/app_notification_manager_unittest.cc',
+ 'browser/extensions/app_notification_storage_unittest.cc',
+ 'browser/extensions/app_notification_test_util.cc',
'browser/extensions/apps_promo_unittest.cc',
'browser/extensions/convert_user_script_unittest.cc',
'browser/extensions/convert_web_app_unittest.cc',
@@ -1804,6 +1807,8 @@
'common/extensions/extension_permission_set_unittest.cc',
'common/extensions/extension_resource_unittest.cc',
'common/extensions/extension_set_unittest.cc',
+ 'common/extensions/extension_test_util.h',
+ 'common/extensions/extension_test_util.cc',
'common/extensions/extension_unittest.cc',
'common/extensions/extension_unpacker_unittest.cc',
'common/extensions/update_manifest_unittest.cc',
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 19c9cbe..263bdd7 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -329,7 +329,8 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
// Generates an extension ID from arbitrary input. The same input string will
// always generate the same output ID.
- static bool GenerateId(const std::string& input, std::string* output);
+ static bool GenerateId(const std::string& input,
+ std::string* output) WARN_UNUSED_RESULT;
// Expects base64 encoded |input| and formats into |output| including
// the appropriate header & footer.
diff --git a/chrome/common/extensions/extension_resource_unittest.cc b/chrome/common/extensions/extension_resource_unittest.cc
index 9424d5f..6f42025 100644
--- a/chrome/common/extensions/extension_resource_unittest.cc
+++ b/chrome/common/extensions/extension_resource_unittest.cc
@@ -11,6 +11,7 @@
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_l10n_util.h"
#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/extension_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
@@ -33,8 +34,7 @@ TEST(ExtensionResourceTest, CreateWithMissingResourceOnDisk) {
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &root_path));
FilePath relative_path;
relative_path = relative_path.AppendASCII("cira.js");
- std::string extension_id;
- Extension::GenerateId("test", &extension_id);
+ std::string extension_id = extension_test_util::MakeId("test");
ExtensionResource resource(extension_id, root_path, relative_path);
// The path doesn't exist on disk, we will be returned an empty path.
@@ -69,8 +69,7 @@ TEST(ExtensionResourceTest, CreateWithAllResourcesOnDisk) {
}
FilePath path;
- std::string extension_id;
- Extension::GenerateId("test", &extension_id);
+ std::string extension_id = extension_test_util::MakeId("test");
ExtensionResource resource(extension_id, temp.path(),
FilePath().AppendASCII(filename));
FilePath resolved_path = resource.GetFilePath();
diff --git a/chrome/common/extensions/extension_test_util.cc b/chrome/common/extensions/extension_test_util.cc
new file mode 100644
index 0000000..31f3af2
--- /dev/null
+++ b/chrome/common/extensions/extension_test_util.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extension_test_util {
+
+std::string MakeId(std::string seed) {
+ std::string result;
+ bool success = Extension::GenerateId(seed, &result);
+ EXPECT_TRUE(success);
+ EXPECT_FALSE(result.empty());
+ EXPECT_TRUE(Extension::IdIsValid(result));
+ return result;
+}
+
+} // namespace extension_test_util
diff --git a/chrome/common/extensions/extension_test_util.h b/chrome/common/extensions/extension_test_util.h
new file mode 100644
index 0000000..77c47e5
--- /dev/null
+++ b/chrome/common/extensions/extension_test_util.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
+#define CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
+#pragma once
+
+#include <string>
+
+namespace extension_test_util {
+
+// Makes a fake extension id using the given |seed|.
+std::string MakeId(std::string seed);
+
+} // namespace extension_test_util
+
+#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_