diff options
author | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-05 05:13:15 +0000 |
---|---|---|
committer | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-05 05:13:15 +0000 |
commit | af9db5fc79084fe986cf51ccc8b3475a5a4ef76f (patch) | |
tree | 119a36be14a106056ddf2e03e4c68c8baddd8977 /chrome | |
parent | 38476438089c4ddba8b698a29e1ea57cea61f90c (diff) | |
download | chromium_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')
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_ |