summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/extension_service_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions/extension_service_unittest.cc')
-rw-r--r--chrome/browser/extensions/extension_service_unittest.cc3254
1 files changed, 3254 insertions, 0 deletions
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
new file mode 100644
index 0000000..b9de0f8
--- /dev/null
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -0,0 +1,3254 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_service_unittest.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/scoped_ptr.h"
+#include "base/stl_util-inl.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/task.h"
+#include "base/utf_string_conversions.h"
+#include "base/version.h"
+#include "chrome/browser/appcache/chrome_appcache_service.h"
+#include "chrome/browser/browser_thread.h"
+#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/extension_creator.h"
+#include "chrome/browser/extensions/extension_error_reporter.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/external_extension_provider.h"
+#include "chrome/browser/extensions/external_pref_extension_provider.h"
+#include "chrome/browser/extensions/pack_extension_job.cc"
+#include "chrome/browser/file_system/browser_file_system_helper.h"
+#include "chrome/browser/in_process_webkit/dom_storage_context.h"
+#include "chrome/browser/in_process_webkit/webkit_context.h"
+#include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/browser/prefs/pref_service_mock_builder.h"
+#include "chrome/browser/prefs/scoped_pref_update.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "chrome/common/json_value_serializer.h"
+#include "chrome/common/net/url_request_context_getter.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/testing_profile.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/cookie_options.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "webkit/database/database_tracker.h"
+#include "webkit/database/database_util.h"
+
+namespace keys = extension_manifest_keys;
+
+namespace {
+
+// Extension ids used during testing.
+const char* const all_zero = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char* const zero_n_one = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab";
+const char* const good0 = "behllobkkfkfnphdnhnkndlbkcpglgmj";
+const char* const good1 = "hpiknbiabeeppbpihjehijgoemciehgk";
+const char* const good2 = "bjafgdebaacbbbecmhlhpofkepfkgcpa";
+const char* const good_crx = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
+const char* const page_action = "obcimlgaoabeegjmmpldobjndiealpln";
+const char* const theme_crx = "iamefpfkojoapidjnbafmgkgncegbkad";
+const char* const theme2_crx = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf";
+const char* const permissions_crx = "eagpmdpfmaekmmcejjbmjoecnejeiiin";
+
+struct ExtensionsOrder {
+ bool operator()(const Extension* a, const Extension* b) {
+ return a->name() < b->name();
+ }
+};
+
+static std::vector<std::string> GetErrors() {
+ const std::vector<std::string>* errors =
+ ExtensionErrorReporter::GetInstance()->GetErrors();
+ std::vector<std::string> ret_val;
+
+ for (std::vector<std::string>::const_iterator iter = errors->begin();
+ iter != errors->end(); ++iter) {
+ if (iter->find(".svn") == std::string::npos) {
+ ret_val.push_back(*iter);
+ }
+ }
+
+ // The tests rely on the errors being in a certain order, which can vary
+ // depending on how filesystem iteration works.
+ std::stable_sort(ret_val.begin(), ret_val.end());
+
+ return ret_val;
+}
+
+static void AddPattern(ExtensionExtent* extent, const std::string& pattern) {
+ int schemes = URLPattern::SCHEME_ALL;
+ extent->AddPattern(URLPattern(schemes, pattern));
+}
+
+static void AssertEqualExtents(ExtensionExtent* extent1,
+ ExtensionExtent* extent2) {
+ std::vector<URLPattern> patterns1 = extent1->patterns();
+ std::vector<URLPattern> patterns2 = extent2->patterns();
+ std::set<std::string> strings1;
+ EXPECT_EQ(patterns1.size(), patterns2.size());
+
+ for (size_t i = 0; i < patterns1.size(); ++i)
+ strings1.insert(patterns1.at(i).GetAsString());
+
+ std::set<std::string> strings2;
+ for (size_t i = 0; i < patterns2.size(); ++i)
+ strings2.insert(patterns2.at(i).GetAsString());
+
+ EXPECT_EQ(strings1, strings2);
+}
+
+} // namespace
+
+class MockExtensionProvider : public ExternalExtensionProvider {
+ public:
+ explicit MockExtensionProvider(Extension::Location location)
+ : location_(location), visit_count_(0) {}
+ virtual ~MockExtensionProvider() {}
+
+ void UpdateOrAddExtension(const std::string& id,
+ const std::string& version,
+ const FilePath& path) {
+ extension_map_[id] = std::make_pair(version, path);
+ }
+
+ void RemoveExtension(const std::string& id) {
+ extension_map_.erase(id);
+ }
+
+ // ExternalExtensionProvider implementation:
+ virtual void VisitRegisteredExtension(Visitor* visitor) const {
+ visit_count_++;
+ for (DataMap::const_iterator i = extension_map_.begin();
+ i != extension_map_.end(); ++i) {
+ scoped_ptr<Version> version;
+ version.reset(Version::GetVersionFromString(i->second.first));
+
+ visitor->OnExternalExtensionFileFound(
+ i->first, version.get(), i->second.second, location_);
+ }
+ }
+
+ virtual bool HasExtension(const std::string& id) const {
+ return extension_map_.find(id) != extension_map_.end();
+ }
+
+ virtual bool GetExtensionDetails(const std::string& id,
+ Extension::Location* location,
+ scoped_ptr<Version>* version) const {
+ DataMap::const_iterator it = extension_map_.find(id);
+ if (it == extension_map_.end())
+ return false;
+
+ if (version)
+ version->reset(Version::GetVersionFromString(it->second.first));
+
+ if (location)
+ *location = location_;
+
+ return true;
+ }
+ int visit_count() const { return visit_count_; }
+ void set_visit_count(int visit_count) {
+ visit_count_ = visit_count;
+ }
+
+ private:
+ typedef std::map< std::string, std::pair<std::string, FilePath> > DataMap;
+ DataMap extension_map_;
+ Extension::Location location_;
+
+ // visit_count_ tracks the number of calls to VisitRegisteredExtension().
+ // Mutable because it must be incremented on each call to
+ // VisitRegisteredExtension(), which must be a const method to inherit
+ // from the class being mocked.
+ mutable int visit_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockExtensionProvider);
+};
+
+class MockProviderVisitor : public ExternalExtensionProvider::Visitor {
+ public:
+ MockProviderVisitor() : ids_found_(0) {
+ }
+
+ int Visit(const std::string& json_data) {
+ // Give the test json file to the provider for parsing.
+ provider_.reset(new ExternalPrefExtensionProvider());
+ provider_->SetPreferencesForTesting(json_data);
+
+ // We also parse the file into a dictionary to compare what we get back
+ // from the provider.
+ JSONStringValueSerializer serializer(json_data);
+ Value* json_value = serializer.Deserialize(NULL, NULL);
+
+ if (!json_value || !json_value->IsType(Value::TYPE_DICTIONARY)) {
+ NOTREACHED() << "Unable to deserialize json data";
+ return -1;
+ } else {
+ DictionaryValue* external_extensions =
+ static_cast<DictionaryValue*>(json_value);
+ prefs_.reset(external_extensions);
+ }
+
+ // Reset our counter.
+ ids_found_ = 0;
+ // Ask the provider to look up all extensions and return them.
+ provider_->VisitRegisteredExtension(this);
+
+ return ids_found_;
+ }
+
+ virtual void OnExternalExtensionFileFound(const std::string& id,
+ const Version* version,
+ const FilePath& path,
+ Extension::Location unused) {
+ ++ids_found_;
+ DictionaryValue* pref;
+ // This tests is to make sure that the provider only notifies us of the
+ // values we gave it. So if the id we doesn't exist in our internal
+ // dictionary then something is wrong.
+ EXPECT_TRUE(prefs_->GetDictionary(id, &pref))
+ << "Got back ID (" << id.c_str() << ") we weren't expecting";
+
+ if (pref) {
+ EXPECT_TRUE(provider_->HasExtension(id));
+
+ // Ask provider if the extension we got back is registered.
+ Extension::Location location = Extension::INVALID;
+ scoped_ptr<Version> v1;
+ FilePath crx_path;
+
+ EXPECT_TRUE(provider_->GetExtensionDetails(id, NULL, &v1));
+ EXPECT_STREQ(version->GetString().c_str(), v1->GetString().c_str());
+
+ scoped_ptr<Version> v2;
+ EXPECT_TRUE(provider_->GetExtensionDetails(id, &location, &v2));
+ EXPECT_STREQ(version->GetString().c_str(), v1->GetString().c_str());
+ EXPECT_STREQ(version->GetString().c_str(), v2->GetString().c_str());
+ EXPECT_EQ(Extension::EXTERNAL_PREF, location);
+
+ // Remove it so we won't count it ever again.
+ prefs_->Remove(id, NULL);
+ }
+ }
+
+ virtual void OnExternalExtensionUpdateUrlFound(
+ const std::string& id, const GURL& update_url,
+ Extension::Location location) {
+ ++ids_found_;
+ DictionaryValue* pref;
+ // This tests is to make sure that the provider only notifies us of the
+ // values we gave it. So if the id we doesn't exist in our internal
+ // dictionary then something is wrong.
+ EXPECT_TRUE(prefs_->GetDictionary(id, &pref))
+ << L"Got back ID (" << id.c_str() << ") we weren't expecting";
+ EXPECT_EQ(Extension::EXTERNAL_PREF_DOWNLOAD, location);
+
+ if (pref) {
+ EXPECT_TRUE(provider_->HasExtension(id));
+
+ // External extensions with update URLs do not have versions.
+ scoped_ptr<Version> v1;
+ Extension::Location location1 = Extension::INVALID;
+ EXPECT_TRUE(provider_->GetExtensionDetails(id, &location1, &v1));
+ EXPECT_FALSE(v1.get());
+ EXPECT_EQ(Extension::EXTERNAL_PREF_DOWNLOAD, location1);
+
+ // Remove it so we won't count it again.
+ prefs_->Remove(id, NULL);
+ }
+ }
+
+ private:
+ int ids_found_;
+
+ scoped_ptr<ExternalPrefExtensionProvider> provider_;
+ scoped_ptr<DictionaryValue> prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockProviderVisitor);
+};
+
+class ExtensionTestingProfile : public TestingProfile {
+ public:
+ ExtensionTestingProfile() : service_(NULL) {
+ }
+
+ void set_extensions_service(ExtensionService* service) {
+ service_ = service;
+ }
+ virtual ExtensionService* GetExtensionService() { return service_; }
+
+ virtual ChromeAppCacheService* GetAppCacheService() {
+ if (!appcache_service_) {
+ appcache_service_ = new ChromeAppCacheService;
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ NewRunnableMethod(appcache_service_.get(),
+ &ChromeAppCacheService::InitializeOnIOThread,
+ GetPath(), IsOffTheRecord(),
+ make_scoped_refptr(GetHostContentSettingsMap())));
+ }
+ return appcache_service_;
+ }
+
+ virtual fileapi::SandboxedFileSystemContext* GetFileSystemContext() {
+ if (!file_system_context_)
+ file_system_context_ = CreateFileSystemContext(
+ GetPath(), IsOffTheRecord());
+ return file_system_context_;
+ }
+
+ private:
+ ExtensionService* service_;
+ scoped_refptr<ChromeAppCacheService> appcache_service_;
+ scoped_refptr<fileapi::SandboxedFileSystemContext> file_system_context_;
+};
+
+// Our message loop may be used in tests which require it to be an IO loop.
+ExtensionServiceTestBase::ExtensionServiceTestBase()
+ : total_successes_(0),
+ loop_(MessageLoop::TYPE_IO),
+ ui_thread_(BrowserThread::UI, &loop_),
+ db_thread_(BrowserThread::DB, &loop_),
+ webkit_thread_(BrowserThread::WEBKIT, &loop_),
+ file_thread_(BrowserThread::FILE, &loop_),
+ io_thread_(BrowserThread::IO, &loop_) {
+}
+
+ExtensionServiceTestBase::~ExtensionServiceTestBase() {
+ // Drop our reference to ExtensionService and TestingProfile, so that they
+ // can be destroyed while BrowserThreads and MessageLoop are still around
+ // (they are used in the destruction process).
+ service_ = NULL;
+ profile_.reset(NULL);
+ MessageLoop::current()->RunAllPending();
+}
+
+void ExtensionServiceTestBase::InitializeExtensionService(
+ const FilePath& pref_file, const FilePath& extensions_install_dir) {
+ ExtensionTestingProfile* profile = new ExtensionTestingProfile();
+ // Create a PrefService that only contains user defined preference values.
+ PrefService* prefs =
+ PrefServiceMockBuilder().WithUserFilePrefs(pref_file).Create();
+ Profile::RegisterUserPrefs(prefs);
+ browser::RegisterUserPrefs(prefs);
+ profile->SetPrefService(prefs);
+
+ profile_.reset(profile);
+
+ service_ = profile->CreateExtensionService(
+ CommandLine::ForCurrentProcess(),
+ extensions_install_dir);
+ service_->set_extensions_enabled(true);
+ service_->set_show_extensions_prompts(false);
+ profile->set_extensions_service(service_.get());
+
+ // When we start up, we want to make sure there is no external provider,
+ // since the ExtensionService on Windows will use the Registry as a default
+ // provider and if there is something already registered there then it will
+ // interfere with the tests. Those tests that need an external provider
+ // will register one specifically.
+ service_->ClearProvidersForTesting();
+
+ total_successes_ = 0;
+}
+
+void ExtensionServiceTestBase::InitializeInstalledExtensionService(
+ const FilePath& prefs_file, const FilePath& source_install_dir) {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ FilePath path_ = temp_dir_.path();
+ path_ = path_.Append(FILE_PATH_LITERAL("TestingExtensionsPath"));
+ file_util::Delete(path_, true);
+ file_util::CreateDirectory(path_);
+ FilePath temp_prefs = path_.Append(FILE_PATH_LITERAL("Preferences"));
+ file_util::CopyFile(prefs_file, temp_prefs);
+
+ extensions_install_dir_ = path_.Append(FILE_PATH_LITERAL("Extensions"));
+ file_util::Delete(extensions_install_dir_, true);
+ file_util::CopyDirectory(source_install_dir, extensions_install_dir_, true);
+
+ InitializeExtensionService(temp_prefs, extensions_install_dir_);
+}
+
+void ExtensionServiceTestBase::InitializeEmptyExtensionService() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ FilePath path_ = temp_dir_.path();
+ path_ = path_.Append(FILE_PATH_LITERAL("TestingExtensionsPath"));
+ file_util::Delete(path_, true);
+ file_util::CreateDirectory(path_);
+ FilePath prefs_filename = path_
+ .Append(FILE_PATH_LITERAL("TestPreferences"));
+ extensions_install_dir_ = path_.Append(FILE_PATH_LITERAL("Extensions"));
+ file_util::Delete(extensions_install_dir_, true);
+ file_util::CreateDirectory(extensions_install_dir_);
+
+ InitializeExtensionService(prefs_filename, extensions_install_dir_);
+}
+
+// static
+void ExtensionServiceTestBase::SetUpTestCase() {
+ ExtensionErrorReporter::Init(false); // no noisy errors
+}
+
+void ExtensionServiceTestBase::SetUp() {
+ ExtensionErrorReporter::GetInstance()->ClearErrors();
+}
+
+class ExtensionServiceTest
+ : public ExtensionServiceTestBase, public NotificationObserver {
+ public:
+ ExtensionServiceTest() : installed_(NULL) {
+ registrar_.Add(this, NotificationType::EXTENSION_LOADED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::EXTENSION_INSTALLED,
+ NotificationService::AllSources());
+ registrar_.Add(this, NotificationType::THEME_INSTALLED,
+ NotificationService::AllSources());
+ }
+
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::EXTENSION_LOADED: {
+ const Extension* extension = Details<const Extension>(details).ptr();
+ loaded_.push_back(make_scoped_refptr(extension));
+ // The tests rely on the errors being in a certain order, which can vary
+ // depending on how filesystem iteration works.
+ std::stable_sort(loaded_.begin(), loaded_.end(), ExtensionsOrder());
+ break;
+ }
+
+ case NotificationType::EXTENSION_UNLOADED: {
+ const Extension* e = Details<const Extension>(details).ptr();
+ unloaded_id_ = e->id();
+ ExtensionList::iterator i =
+ std::find(loaded_.begin(), loaded_.end(), e);
+ // TODO(erikkay) fix so this can be an assert. Right now the tests
+ // are manually calling clear() on loaded_, so this isn't doable.
+ if (i == loaded_.end())
+ return;
+ loaded_.erase(i);
+ break;
+ }
+ case NotificationType::EXTENSION_INSTALLED:
+ case NotificationType::THEME_INSTALLED:
+ installed_ = Details<const Extension>(details).ptr();
+ break;
+
+ default:
+ DCHECK(false);
+ }
+ }
+
+ void AddMockExternalProvider(ExternalExtensionProvider* provider) {
+ service_->AddProviderForTesting(provider);
+ }
+
+ protected:
+ void TestExternalProvider(MockExtensionProvider* provider,
+ Extension::Location location);
+
+ void PackAndInstallExtension(const FilePath& dir_path,
+ const FilePath& pem_path,
+ bool should_succeed) {
+ FilePath crx_path;
+ ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &crx_path));
+ crx_path = crx_path.AppendASCII("temp.crx");
+
+ // Use the existing pem key, if provided.
+ FilePath pem_output_path;
+ if (pem_path.value().empty()) {
+ pem_output_path = crx_path.DirName().AppendASCII("temp.pem");
+ ASSERT_TRUE(file_util::Delete(pem_output_path, false));
+ } else {
+ ASSERT_TRUE(file_util::PathExists(pem_path));
+ }
+
+ ASSERT_TRUE(file_util::Delete(crx_path, false));
+
+ scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
+ ASSERT_TRUE(creator->Run(dir_path,
+ crx_path,
+ pem_path,
+ pem_output_path));
+
+ ASSERT_TRUE(file_util::PathExists(crx_path));
+
+ InstallExtension(crx_path, should_succeed);
+ }
+
+ void PackAndInstallExtension(const FilePath& dir_path,
+ bool should_succeed) {
+ PackAndInstallExtension(dir_path, FilePath(), should_succeed);
+ }
+
+ void InstallExtension(const FilePath& path,
+ bool should_succeed) {
+ ASSERT_TRUE(file_util::PathExists(path));
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+ std::vector<std::string> errors = GetErrors();
+ if (should_succeed) {
+ ++total_successes_;
+
+ EXPECT_TRUE(installed_) << path.value();
+
+ ASSERT_EQ(1u, loaded_.size()) << path.value();
+ EXPECT_EQ(0u, errors.size()) << path.value();
+ EXPECT_EQ(total_successes_, service_->extensions()->size()) <<
+ path.value();
+ EXPECT_TRUE(service_->GetExtensionById(loaded_[0]->id(), false)) <<
+ path.value();
+ for (std::vector<std::string>::iterator err = errors.begin();
+ err != errors.end(); ++err) {
+ LOG(ERROR) << *err;
+ }
+ } else {
+ EXPECT_FALSE(installed_) << path.value();
+ EXPECT_EQ(0u, loaded_.size()) << path.value();
+ EXPECT_EQ(1u, errors.size()) << path.value();
+ }
+
+ installed_ = NULL;
+ loaded_.clear();
+ ExtensionErrorReporter::GetInstance()->ClearErrors();
+ }
+
+ enum UpdateState {
+ FAILED_SILENTLY,
+ FAILED,
+ UPDATED,
+ INSTALLED,
+ ENABLED
+ };
+
+ void UpdateExtension(const std::string& id, const FilePath& in_path,
+ UpdateState expected_state) {
+ ASSERT_TRUE(file_util::PathExists(in_path));
+
+ // We need to copy this to a temporary location because Update() will delete
+ // it.
+ FilePath path = temp_dir_.path();
+ path = path.Append(in_path.BaseName());
+ ASSERT_TRUE(file_util::CopyFile(in_path, path));
+
+ int previous_enabled_extension_count =
+ service_->extensions()->size();
+ int previous_installed_extension_count =
+ previous_enabled_extension_count +
+ service_->disabled_extensions()->size();
+
+ service_->UpdateExtension(id, path, GURL());
+ loop_.RunAllPending();
+
+ std::vector<std::string> errors = GetErrors();
+ int error_count = errors.size();
+ int enabled_extension_count =
+ service_->extensions()->size();
+ int installed_extension_count =
+ enabled_extension_count + service_->disabled_extensions()->size();
+
+ int expected_error_count = (expected_state == FAILED) ? 1 : 0;
+ EXPECT_EQ(expected_error_count, error_count) << path.value();
+
+ if (expected_state <= FAILED) {
+ EXPECT_EQ(previous_enabled_extension_count,
+ enabled_extension_count);
+ EXPECT_EQ(previous_installed_extension_count,
+ installed_extension_count);
+ } else {
+ int expected_installed_extension_count =
+ (expected_state >= INSTALLED) ? 1 : 0;
+ int expected_enabled_extension_count =
+ (expected_state >= ENABLED) ? 1 : 0;
+ EXPECT_EQ(expected_installed_extension_count,
+ installed_extension_count);
+ EXPECT_EQ(expected_enabled_extension_count,
+ enabled_extension_count);
+ }
+
+ // Update() should delete the temporary input file.
+ EXPECT_FALSE(file_util::PathExists(path));
+ }
+
+ void ValidatePrefKeyCount(size_t count) {
+ DictionaryValue* dict =
+ profile_->GetPrefs()->GetMutableDictionary("extensions.settings");
+ ASSERT_TRUE(dict != NULL);
+ EXPECT_EQ(count, dict->size());
+ }
+
+ void ValidateBooleanPref(const std::string& extension_id,
+ const std::string& pref_path,
+ bool expected_val) {
+ std::string msg = " while checking: ";
+ msg += extension_id;
+ msg += " ";
+ msg += pref_path;
+ msg += " == ";
+ msg += expected_val ? "true" : "false";
+
+ PrefService* prefs = profile_->GetPrefs();
+ const DictionaryValue* dict =
+ prefs->GetDictionary("extensions.settings");
+ ASSERT_TRUE(dict != NULL) << msg;
+ DictionaryValue* pref = NULL;
+ ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg;
+ EXPECT_TRUE(pref != NULL) << msg;
+ bool val;
+ ASSERT_TRUE(pref->GetBoolean(pref_path, &val)) << msg;
+ EXPECT_EQ(expected_val, val) << msg;
+ }
+
+ bool IsPrefExist(const std::string& extension_id,
+ const std::string& pref_path) {
+ const DictionaryValue* dict =
+ profile_->GetPrefs()->GetDictionary("extensions.settings");
+ if (dict == NULL) return false;
+ DictionaryValue* pref = NULL;
+ if (!dict->GetDictionary(extension_id, &pref)) {
+ return false;
+ }
+ if (pref == NULL) {
+ return false;
+ }
+ bool val;
+ if (!pref->GetBoolean(pref_path, &val)) {
+ return false;
+ }
+ return true;
+ }
+
+ void ValidateIntegerPref(const std::string& extension_id,
+ const std::string& pref_path,
+ int expected_val) {
+ std::string msg = " while checking: ";
+ msg += extension_id;
+ msg += " ";
+ msg += pref_path;
+ msg += " == ";
+ msg += base::IntToString(expected_val);
+
+ PrefService* prefs = profile_->GetPrefs();
+ const DictionaryValue* dict =
+ prefs->GetDictionary("extensions.settings");
+ ASSERT_TRUE(dict != NULL) << msg;
+ DictionaryValue* pref = NULL;
+ ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg;
+ EXPECT_TRUE(pref != NULL) << msg;
+ int val;
+ ASSERT_TRUE(pref->GetInteger(pref_path, &val)) << msg;
+ EXPECT_EQ(expected_val, val) << msg;
+ }
+
+ void ValidateStringPref(const std::string& extension_id,
+ const std::string& pref_path,
+ const std::string& expected_val) {
+ std::string msg = " while checking: ";
+ msg += extension_id;
+ msg += ".manifest.";
+ msg += pref_path;
+ msg += " == ";
+ msg += expected_val;
+
+ const DictionaryValue* dict =
+ profile_->GetPrefs()->GetDictionary("extensions.settings");
+ ASSERT_TRUE(dict != NULL) << msg;
+ DictionaryValue* pref = NULL;
+ std::string manifest_path = extension_id + ".manifest";
+ ASSERT_TRUE(dict->GetDictionary(manifest_path, &pref)) << msg;
+ EXPECT_TRUE(pref != NULL) << msg;
+ std::string val;
+ ASSERT_TRUE(pref->GetString(pref_path, &val)) << msg;
+ EXPECT_EQ(expected_val, val) << msg;
+ }
+
+ void SetPref(const std::string& extension_id,
+ const std::string& pref_path,
+ Value* value,
+ const std::string& msg) {
+ const DictionaryValue* dict =
+ profile_->GetPrefs()->GetMutableDictionary("extensions.settings");
+ ASSERT_TRUE(dict != NULL) << msg;
+ DictionaryValue* pref = NULL;
+ ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg;
+ EXPECT_TRUE(pref != NULL) << msg;
+ pref->Set(pref_path, value);
+ }
+
+ void SetPrefInteg(const std::string& extension_id,
+ const std::string& pref_path,
+ int value) {
+ std::string msg = " while setting: ";
+ msg += extension_id;
+ msg += " ";
+ msg += pref_path;
+ msg += " = ";
+ msg += base::IntToString(value);
+
+ SetPref(extension_id, pref_path, Value::CreateIntegerValue(value), msg);
+ }
+
+ void SetPrefBool(const std::string& extension_id,
+ const std::string& pref_path,
+ bool value) {
+ std::string msg = " while setting: ";
+ msg += extension_id + " " + pref_path;
+ msg += " = ";
+ msg += (value ? "true" : "false");
+
+ SetPref(extension_id, pref_path, Value::CreateBooleanValue(value), msg);
+ }
+
+ void ClearPref(const std::string& extension_id,
+ const std::string& pref_path) {
+ std::string msg = " while clearing: ";
+ msg += extension_id + " " + pref_path;
+
+ const DictionaryValue* dict =
+ profile_->GetPrefs()->GetMutableDictionary("extensions.settings");
+ ASSERT_TRUE(dict != NULL) << msg;
+ DictionaryValue* pref = NULL;
+ ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg;
+ EXPECT_TRUE(pref != NULL) << msg;
+ pref->Remove(pref_path, NULL);
+ }
+
+ void SetPrefStringSet(const std::string& extension_id,
+ const std::string& pref_path,
+ const std::set<std::string>& value) {
+ std::string msg = " while setting: ";
+ msg += extension_id + " " + pref_path;
+
+ ListValue* list_value = new ListValue();
+ for (std::set<std::string>::const_iterator iter = value.begin();
+ iter != value.end(); ++iter)
+ list_value->Append(Value::CreateStringValue(*iter));
+
+ SetPref(extension_id, pref_path, list_value, msg);
+ }
+
+ protected:
+ ExtensionList loaded_;
+ std::string unloaded_id_;
+ const Extension* installed_;
+
+ private:
+ NotificationRegistrar registrar_;
+};
+
+FilePath NormalizeSeparators(const FilePath& path) {
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ return path.NormalizeWindowsPathSeparators();
+#else
+ return path;
+#endif // FILE_PATH_USES_WIN_SEPARATORS
+}
+
+// Receives notifications from a PackExtensionJob, indicating either that
+// packing succeeded or that there was some error.
+class PackExtensionTestClient : public PackExtensionJob::Client {
+ public:
+ PackExtensionTestClient(const FilePath& expected_crx_path,
+ const FilePath& expected_private_key_path);
+ virtual void OnPackSuccess(const FilePath& crx_path,
+ const FilePath& private_key_path);
+ virtual void OnPackFailure(const std::string& error_message);
+
+ private:
+ const FilePath expected_crx_path_;
+ const FilePath expected_private_key_path_;
+ DISALLOW_COPY_AND_ASSIGN(PackExtensionTestClient);
+};
+
+PackExtensionTestClient::PackExtensionTestClient(
+ const FilePath& expected_crx_path,
+ const FilePath& expected_private_key_path)
+ : expected_crx_path_(expected_crx_path),
+ expected_private_key_path_(expected_private_key_path) {}
+
+// If packing succeeded, we make sure that the package names match our
+// expectations.
+void PackExtensionTestClient::OnPackSuccess(const FilePath& crx_path,
+ const FilePath& private_key_path) {
+ // We got the notification and processed it; we don't expect any further tasks
+ // to be posted to the current thread, so we should stop blocking and continue
+ // on with the rest of the test.
+ // This call to |Quit()| matches the call to |Run()| in the
+ // |PackPunctuatedExtension| test.
+ MessageLoop::current()->Quit();
+ EXPECT_EQ(expected_crx_path_.value(), crx_path.value());
+ EXPECT_EQ(expected_private_key_path_.value(), private_key_path.value());
+ ASSERT_TRUE(file_util::PathExists(private_key_path));
+}
+
+// The tests are designed so that we never expect to see a packing error.
+void PackExtensionTestClient::OnPackFailure(const std::string& error_message) {
+ FAIL() << "Packing should not fail.";
+}
+
+// Test loading good extensions from the profile directory.
+TEST_F(ExtensionServiceTest, LoadAllExtensionsFromDirectorySuccess) {
+ // Initialize the test dir with a good Preferences/extensions.
+ FilePath source_install_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir));
+ source_install_dir = source_install_dir
+ .AppendASCII("extensions")
+ .AppendASCII("good")
+ .AppendASCII("Extensions");
+ FilePath pref_path = source_install_dir
+ .DirName()
+ .AppendASCII("Preferences");
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ service_->Init();
+
+ // On Chrome OS, we disallow extensions with plugins. "good1" has plugins,
+ // so we need to edit it out here.
+ uint32 expected_num_extensions = 3u;
+#if defined(OS_CHROMEOS)
+ --expected_num_extensions;
+#endif
+ ASSERT_EQ(expected_num_extensions, loaded_.size());
+
+ EXPECT_EQ(std::string(good0), loaded_[0]->id());
+ EXPECT_EQ(std::string("My extension 1"),
+ loaded_[0]->name());
+ EXPECT_EQ(std::string("The first extension that I made."),
+ loaded_[0]->description());
+ EXPECT_EQ(Extension::INTERNAL, loaded_[0]->location());
+ EXPECT_TRUE(service_->GetExtensionById(loaded_[0]->id(), false));
+ EXPECT_EQ(expected_num_extensions, service_->extensions()->size());
+
+ ValidatePrefKeyCount(3);
+ ValidateIntegerPref(good0, "state", Extension::ENABLED);
+ ValidateIntegerPref(good0, "location", Extension::INTERNAL);
+ ValidateIntegerPref(good1, "state", Extension::ENABLED);
+ ValidateIntegerPref(good1, "location", Extension::INTERNAL);
+ ValidateIntegerPref(good2, "state", Extension::ENABLED);
+ ValidateIntegerPref(good2, "location", Extension::INTERNAL);
+
+ const Extension* extension = loaded_[0];
+ const UserScriptList& scripts = extension->content_scripts();
+ ASSERT_EQ(2u, scripts.size());
+ EXPECT_EQ(3u, scripts[0].url_patterns().size());
+ EXPECT_EQ("file://*",
+ scripts[0].url_patterns()[0].GetAsString());
+ EXPECT_EQ("http://*.google.com/*",
+ scripts[0].url_patterns()[1].GetAsString());
+ EXPECT_EQ("https://*.google.com/*",
+ scripts[0].url_patterns()[2].GetAsString());
+ EXPECT_EQ(2u, scripts[0].js_scripts().size());
+ ExtensionResource resource00(extension->id(),
+ scripts[0].js_scripts()[0].extension_root(),
+ scripts[0].js_scripts()[0].relative_path());
+ FilePath expected_path(extension->path().AppendASCII("script1.js"));
+ ASSERT_TRUE(file_util::AbsolutePath(&expected_path));
+ EXPECT_TRUE(resource00.ComparePathWithDefault(expected_path));
+ ExtensionResource resource01(extension->id(),
+ scripts[0].js_scripts()[1].extension_root(),
+ scripts[0].js_scripts()[1].relative_path());
+ expected_path = extension->path().AppendASCII("script2.js");
+ ASSERT_TRUE(file_util::AbsolutePath(&expected_path));
+ EXPECT_TRUE(resource01.ComparePathWithDefault(expected_path));
+ EXPECT_TRUE(extension->plugins().empty());
+ EXPECT_EQ(1u, scripts[1].url_patterns().size());
+ EXPECT_EQ("http://*.news.com/*", scripts[1].url_patterns()[0].GetAsString());
+ ExtensionResource resource10(extension->id(),
+ scripts[1].js_scripts()[0].extension_root(),
+ scripts[1].js_scripts()[0].relative_path());
+ expected_path =
+ extension->path().AppendASCII("js_files").AppendASCII("script3.js");
+ ASSERT_TRUE(file_util::AbsolutePath(&expected_path));
+ EXPECT_TRUE(resource10.ComparePathWithDefault(expected_path));
+ const std::vector<URLPattern> permissions = extension->host_permissions();
+ ASSERT_EQ(2u, permissions.size());
+ EXPECT_EQ("http://*.google.com/*", permissions[0].GetAsString());
+ EXPECT_EQ("https://*.google.com/*", permissions[1].GetAsString());
+
+#if !defined(OS_CHROMEOS)
+ EXPECT_EQ(std::string(good1), loaded_[1]->id());
+ EXPECT_EQ(std::string("My extension 2"), loaded_[1]->name());
+ EXPECT_EQ(std::string(""), loaded_[1]->description());
+ EXPECT_EQ(loaded_[1]->GetResourceURL("background.html"),
+ loaded_[1]->background_url());
+ EXPECT_EQ(0u, loaded_[1]->content_scripts().size());
+ EXPECT_EQ(2u, loaded_[1]->plugins().size());
+ EXPECT_EQ(loaded_[1]->path().AppendASCII("content_plugin.dll").value(),
+ loaded_[1]->plugins()[0].path.value());
+ EXPECT_TRUE(loaded_[1]->plugins()[0].is_public);
+ EXPECT_EQ(loaded_[1]->path().AppendASCII("extension_plugin.dll").value(),
+ loaded_[1]->plugins()[1].path.value());
+ EXPECT_FALSE(loaded_[1]->plugins()[1].is_public);
+ EXPECT_EQ(Extension::INTERNAL, loaded_[1]->location());
+#endif
+
+ int index = expected_num_extensions - 1;
+ EXPECT_EQ(std::string(good2), loaded_[index]->id());
+ EXPECT_EQ(std::string("My extension 3"), loaded_[index]->name());
+ EXPECT_EQ(std::string(""), loaded_[index]->description());
+ EXPECT_EQ(0u, loaded_[index]->content_scripts().size());
+ EXPECT_EQ(Extension::INTERNAL, loaded_[index]->location());
+};
+
+// Test loading bad extensions from the profile directory.
+TEST_F(ExtensionServiceTest, LoadAllExtensionsFromDirectoryFail) {
+ // Initialize the test dir with a bad Preferences/extensions.
+ FilePath source_install_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir));
+ source_install_dir = source_install_dir
+ .AppendASCII("extensions")
+ .AppendASCII("bad")
+ .AppendASCII("Extensions");
+ FilePath pref_path = source_install_dir
+ .DirName()
+ .AppendASCII("Preferences");
+
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ service_->Init();
+ loop_.RunAllPending();
+
+ ASSERT_EQ(4u, GetErrors().size());
+ ASSERT_EQ(0u, loaded_.size());
+
+ EXPECT_TRUE(MatchPattern(GetErrors()[0],
+ std::string("Could not load extension from '*'. ") +
+ extension_manifest_errors::kManifestUnreadable)) << GetErrors()[0];
+
+ EXPECT_TRUE(MatchPattern(GetErrors()[1],
+ std::string("Could not load extension from '*'. ") +
+ extension_manifest_errors::kManifestUnreadable)) << GetErrors()[1];
+
+ EXPECT_TRUE(MatchPattern(GetErrors()[2],
+ std::string("Could not load extension from '*'. ") +
+ extension_manifest_errors::kMissingFile)) << GetErrors()[2];
+
+ EXPECT_TRUE(MatchPattern(GetErrors()[3],
+ std::string("Could not load extension from '*'. ") +
+ extension_manifest_errors::kManifestUnreadable)) << GetErrors()[3];
+};
+
+// Test that partially deleted extensions are cleaned up during startup
+// Test loading bad extensions from the profile directory.
+TEST_F(ExtensionServiceTest, CleanupOnStartup) {
+ FilePath source_install_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir));
+ source_install_dir = source_install_dir
+ .AppendASCII("extensions")
+ .AppendASCII("good")
+ .AppendASCII("Extensions");
+ FilePath pref_path = source_install_dir
+ .DirName()
+ .AppendASCII("Preferences");
+
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ // Simulate that one of them got partially deleted by clearing its pref.
+ DictionaryValue* dict =
+ profile_->GetPrefs()->GetMutableDictionary("extensions.settings");
+ ASSERT_TRUE(dict != NULL);
+ dict->Remove("behllobkkfkfnphdnhnkndlbkcpglgmj", NULL);
+
+ service_->Init();
+ loop_.RunAllPending();
+
+ file_util::FileEnumerator dirs(extensions_install_dir_, false,
+ file_util::FileEnumerator::DIRECTORIES);
+ size_t count = 0;
+ while (!dirs.Next().empty())
+ count++;
+
+ // We should have only gotten two extensions now.
+ EXPECT_EQ(2u, count);
+
+ // And extension1 dir should now be toast.
+ FilePath extension_dir = extensions_install_dir_
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj");
+ ASSERT_FALSE(file_util::PathExists(extension_dir));
+}
+
+// Test installing extensions. This test tries to install few extensions using
+// crx files. If you need to change those crx files, feel free to repackage
+// them, throw away the key used and change the id's above.
+TEST_F(ExtensionServiceTest, InstallExtension) {
+ InitializeEmptyExtensionService();
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // Extensions not enabled.
+ set_extensions_enabled(false);
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, false);
+ set_extensions_enabled(true);
+
+ ValidatePrefKeyCount(0);
+
+ // A simple extension that should install without error.
+ path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+ // TODO(erikkay): verify the contents of the installed extension.
+
+ int pref_count = 0;
+ ValidatePrefKeyCount(++pref_count);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", Extension::INTERNAL);
+
+ // An extension with page actions.
+ path = extensions_path.AppendASCII("page_action.crx");
+ InstallExtension(path, true);
+ ValidatePrefKeyCount(++pref_count);
+ ValidateIntegerPref(page_action, "state", Extension::ENABLED);
+ ValidateIntegerPref(page_action, "location", Extension::INTERNAL);
+
+ // Bad signature.
+ path = extensions_path.AppendASCII("bad_signature.crx");
+ InstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
+
+ // 0-length extension file.
+ path = extensions_path.AppendASCII("not_an_extension.crx");
+ InstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
+
+ // Bad magic number.
+ path = extensions_path.AppendASCII("bad_magic.crx");
+ InstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
+
+ // Extensions cannot have folders or files that have underscores except in
+ // certain whitelisted cases (eg _locales). This is an example of a broader
+ // class of validation that we do to the directory structure of the extension.
+ // We did not used to handle this correctly for installation.
+ path = extensions_path.AppendASCII("bad_underscore.crx");
+ InstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
+
+ // TODO(erikkay): add more tests for many of the failure cases.
+ // TODO(erikkay): add tests for upgrade cases.
+}
+
+// Test the handling of killed extensions.
+TEST_F(ExtensionServiceTest, KilledExtensions) {
+ InitializeEmptyExtensionService();
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ set_extensions_enabled(true);
+
+ // Install an external extension.
+ service_->OnExternalExtensionFileFound(good_crx, "1.0.0.0",
+ path, Extension::EXTERNAL_PREF);
+ loop_.RunAllPending();
+ ASSERT_TRUE(NULL != service_->GetExtensionById(good_crx, false));
+
+ // Uninstall it and check that its killbit gets set.
+ service_->UninstallExtension(good_crx, false);
+ loop_.RunAllPending();
+ ValidateIntegerPref(good_crx, "location", Extension::KILLBIT);
+
+ // Try to re-install it externally. This should fail because of the killbit.
+ service_->OnExternalExtensionFileFound(good_crx, "1.0.0.0",
+ path, Extension::EXTERNAL_PREF);
+ loop_.RunAllPending();
+ ASSERT_TRUE(NULL == service_->GetExtensionById(good_crx, false));
+ ValidateIntegerPref(good_crx, "location", Extension::KILLBIT);
+
+ // Repeat the same thing with a newer version of the extension.
+ path = extensions_path.AppendASCII("good2.crx");
+ service_->OnExternalExtensionFileFound(good_crx, "1.0.0.1",
+ path, Extension::EXTERNAL_PREF);
+ loop_.RunAllPending();
+ ASSERT_TRUE(NULL == service_->GetExtensionById(good_crx, false));
+ ValidateIntegerPref(good_crx, "location", Extension::KILLBIT);
+
+ // Try adding the same extension from an external update URL.
+ service_->AddPendingExtensionFromExternalUpdateUrl(
+ good_crx,
+ GURL("http:://fake.update/url"),
+ Extension::EXTERNAL_PREF_DOWNLOAD);
+ const PendingExtensionMap& pending_extensions =
+ service_->pending_extensions();
+ ASSERT_TRUE(pending_extensions.find(good_crx) == pending_extensions.end());
+}
+
+// Install a user script (they get converted automatically to an extension)
+TEST_F(ExtensionServiceTest, InstallUserScript) {
+ // The details of script conversion are tested elsewhere, this just tests
+ // integration with ExtensionService.
+ InitializeEmptyExtensionService();
+
+ FilePath path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions")
+ .AppendASCII("user_script_basic.user.js");
+
+ ASSERT_TRUE(file_util::PathExists(path));
+ scoped_refptr<CrxInstaller> installer(
+ new CrxInstaller(service_, NULL)); // silent install
+ installer->InstallUserScript(
+ path,
+ GURL("http://www.aaronboodman.com/scripts/user_script_basic.user.js"));
+
+ loop_.RunAllPending();
+ std::vector<std::string> errors = GetErrors();
+ EXPECT_TRUE(installed_) << "Nothing was installed.";
+ ASSERT_EQ(1u, loaded_.size()) << "Nothing was loaded.";
+ EXPECT_EQ(0u, errors.size()) << "There were errors: "
+ << JoinString(errors, ',');
+ EXPECT_TRUE(service_->GetExtensionById(loaded_[0]->id(), false)) <<
+ path.value();
+
+ installed_ = NULL;
+ loaded_.clear();
+ ExtensionErrorReporter::GetInstance()->ClearErrors();
+}
+
+// This tests that the granted permissions preferences are correctly set when
+// installing an extension.
+TEST_F(ExtensionServiceTest, GrantedPermissions) {
+ InitializeEmptyExtensionService();
+ FilePath path;
+ FilePath pem_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions")
+ .AppendASCII("permissions");
+
+ pem_path = path.AppendASCII("unknown.pem");
+ path = path.AppendASCII("unknown");
+
+ ASSERT_TRUE(file_util::PathExists(pem_path));
+ ASSERT_TRUE(file_util::PathExists(path));
+
+ ExtensionPrefs* prefs = service_->extension_prefs();
+
+ std::set<std::string> expected_api_perms;
+ std::set<std::string> known_api_perms;
+ bool full_access;
+ ExtensionExtent expected_host_perms;
+ ExtensionExtent known_host_perms;
+
+ // Make sure there aren't any granted permissions before the
+ // extension is installed.
+ EXPECT_FALSE(prefs->GetGrantedPermissions(
+ permissions_crx, &full_access, &known_api_perms, &known_host_perms));
+ EXPECT_TRUE(known_api_perms.empty());
+ EXPECT_TRUE(known_host_perms.is_empty());
+
+ PackAndInstallExtension(path, pem_path, true);
+
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, service_->extensions()->size());
+ std::string extension_id = service_->extensions()->at(0)->id();
+ EXPECT_EQ(permissions_crx, extension_id);
+
+
+ // Verify that the valid API permissions have been recognized.
+ expected_api_perms.insert("tabs");
+
+ AddPattern(&expected_host_perms, "http://*.google.com/*");
+ AddPattern(&expected_host_perms, "https://*.google.com/*");
+ AddPattern(&expected_host_perms, "http://*.google.com.hk/*");
+ AddPattern(&expected_host_perms, "http://www.example.com/*");
+
+ EXPECT_TRUE(prefs->GetGrantedPermissions(extension_id,
+ &full_access,
+ &known_api_perms,
+ &known_host_perms));
+
+ EXPECT_EQ(expected_api_perms, known_api_perms);
+ EXPECT_FALSE(full_access);
+ AssertEqualExtents(&expected_host_perms, &known_host_perms);
+}
+
+#if !defined(OS_CHROMEOS)
+// Tests that the granted permissions full_access bit gets set correctly when
+// an extension contains an NPAPI plugin. Don't run this test on Chrome OS
+// since they don't support plugins.
+TEST_F(ExtensionServiceTest, GrantedFullAccessPermissions) {
+ InitializeEmptyExtensionService();
+
+ FilePath path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions")
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII(good1)
+ .AppendASCII("2");
+
+ ASSERT_TRUE(file_util::PathExists(path));
+
+ PackAndInstallExtension(path, true);
+
+ EXPECT_EQ(0u, GetErrors().size());
+ EXPECT_EQ(1u, service_->extensions()->size());
+ const Extension* extension = service_->extensions()->at(0);
+ std::string extension_id = extension->id();
+ ExtensionPrefs* prefs = service_->extension_prefs();
+
+ bool full_access;
+ std::set<std::string> api_permissions;
+ ExtensionExtent host_permissions;
+ EXPECT_TRUE(prefs->GetGrantedPermissions(
+ extension_id, &full_access, &api_permissions, &host_permissions));
+
+ EXPECT_TRUE(full_access);
+ EXPECT_TRUE(api_permissions.empty());
+ EXPECT_TRUE(host_permissions.is_empty());
+}
+#endif
+
+// Tests that the extension is disabled when permissions are missing from
+// the extension's granted permissions preferences. (This simulates updating
+// the browser to a version which recognizes more permissions).
+TEST_F(ExtensionServiceTest, GrantedAPIAndHostPermissions) {
+ InitializeEmptyExtensionService();
+
+ FilePath path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions")
+ .AppendASCII("permissions")
+ .AppendASCII("unknown");
+
+ ASSERT_TRUE(file_util::PathExists(path));
+
+ PackAndInstallExtension(path, true);
+
+ EXPECT_EQ(0u, GetErrors().size());
+ EXPECT_EQ(1u, service_->extensions()->size());
+ const Extension* extension = service_->extensions()->at(0);
+ std::string extension_id = extension->id();
+
+ ExtensionPrefs* prefs = service_->extension_prefs();
+
+ std::set<std::string> expected_api_permissions;
+ ExtensionExtent expected_host_permissions;
+
+ expected_api_permissions.insert("tabs");
+ AddPattern(&expected_host_permissions, "http://*.google.com/*");
+ AddPattern(&expected_host_permissions, "https://*.google.com/*");
+ AddPattern(&expected_host_permissions, "http://*.google.com.hk/*");
+ AddPattern(&expected_host_permissions, "http://www.example.com/*");
+
+ std::set<std::string> api_permissions;
+ std::set<std::string> host_permissions;
+
+ // Test that the extension is disabled when an API permission is missing from
+ // the extension's granted api permissions preference. (This simulates
+ // updating the browser to a version which recognizes a new API permission).
+ SetPrefStringSet(extension_id, "granted_permissions.api", api_permissions);
+
+ service_->ReloadExtensions();
+
+ EXPECT_EQ(1u, service_->disabled_extensions()->size());
+ extension = service_->disabled_extensions()->at(0);
+
+ ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::DISABLED);
+ ASSERT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+ // Now grant and re-enable the extension, making sure the prefs are updated.
+ service_->GrantPermissionsAndEnableExtension(extension);
+
+ ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED);
+ ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+ std::set<std::string> current_api_permissions;
+ ExtensionExtent current_host_permissions;
+ bool current_full_access;
+
+ ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id,
+ &current_full_access,
+ &current_api_permissions,
+ &current_host_permissions));
+
+ ASSERT_FALSE(current_full_access);
+ ASSERT_EQ(expected_api_permissions, current_api_permissions);
+ AssertEqualExtents(&expected_host_permissions, &current_host_permissions);
+
+ // Tests that the extension is disabled when a host permission is missing from
+ // the extension's granted host permissions preference. (This simulates
+ // updating the browser to a version which recognizes additional host
+ // permissions).
+ api_permissions.clear();
+ host_permissions.clear();
+ current_api_permissions.clear();
+ current_host_permissions.ClearPaths();
+
+ api_permissions.insert("tabs");
+ host_permissions.insert("http://*.google.com/*");
+ host_permissions.insert("https://*.google.com/*");
+ host_permissions.insert("http://*.google.com.hk/*");
+
+ SetPrefStringSet(extension_id, "granted_permissions.api", api_permissions);
+ SetPrefStringSet(extension_id, "granted_permissions.host", host_permissions);
+
+ service_->ReloadExtensions();
+
+ EXPECT_EQ(1u, service_->disabled_extensions()->size());
+ extension = service_->disabled_extensions()->at(0);
+
+ ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::DISABLED);
+ ASSERT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+ // Now grant and re-enable the extension, making sure the prefs are updated.
+ service_->GrantPermissionsAndEnableExtension(extension);
+
+ ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED);
+ ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+ ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id,
+ &current_full_access,
+ &current_api_permissions,
+ &current_host_permissions));
+
+ ASSERT_FALSE(current_full_access);
+ ASSERT_EQ(expected_api_permissions, current_api_permissions);
+ AssertEqualExtents(&expected_host_permissions, &current_host_permissions);
+
+ // Tests that the granted permissions preferences are initialized when
+ // migrating from the old pref schema.
+ current_api_permissions.clear();
+ current_host_permissions.ClearPaths();
+
+ ClearPref(extension_id, "granted_permissions");
+
+ service_->ReloadExtensions();
+
+ EXPECT_EQ(1u, service_->extensions()->size());
+ extension = service_->extensions()->at(0);
+
+ ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED);
+ ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id));
+
+ ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id,
+ &current_full_access,
+ &current_api_permissions,
+ &current_host_permissions));
+
+ ASSERT_FALSE(current_full_access);
+ ASSERT_EQ(expected_api_permissions, current_api_permissions);
+ AssertEqualExtents(&expected_host_permissions, &current_host_permissions);
+}
+
+// Test Packaging and installing an extension.
+TEST_F(ExtensionServiceTest, PackExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath input_directory = extensions_path
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+ .AppendASCII("1.0.0.0");
+
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath output_directory = temp_dir.path();
+
+ FilePath crx_path(output_directory.AppendASCII("ex1.crx"));
+ FilePath privkey_path(output_directory.AppendASCII("privkey.pem"));
+
+ scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
+ ASSERT_TRUE(creator->Run(input_directory, crx_path, FilePath(),
+ privkey_path));
+
+ ASSERT_TRUE(file_util::PathExists(privkey_path));
+ InstallExtension(crx_path, true);
+
+ // Try packing with invalid paths.
+ creator.reset(new ExtensionCreator());
+ ASSERT_FALSE(creator->Run(FilePath(), FilePath(), FilePath(), FilePath()));
+
+ // Try packing an empty directory. Should fail because an empty directory is
+ // not a valid extension.
+ ScopedTempDir temp_dir2;
+ ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
+ creator.reset(new ExtensionCreator());
+ ASSERT_FALSE(creator->Run(temp_dir2.path(), crx_path, privkey_path,
+ FilePath()));
+
+ // Try packing with an invalid manifest.
+ std::string invalid_manifest_content = "I am not a manifest.";
+ ASSERT_TRUE(file_util::WriteFile(
+ temp_dir2.path().Append(Extension::kManifestFilename),
+ invalid_manifest_content.c_str(), invalid_manifest_content.size()));
+ creator.reset(new ExtensionCreator());
+ ASSERT_FALSE(creator->Run(temp_dir2.path(), crx_path, privkey_path,
+ FilePath()));
+}
+
+// Test Packaging and installing an extension whose name contains punctuation.
+TEST_F(ExtensionServiceTest, PackPunctuatedExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath input_directory = extensions_path
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII(good0)
+ .AppendASCII("1.0.0.0");
+
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Extension names containing punctuation, and the expected names for the
+ // packed extensions.
+ const FilePath punctuated_names[] = {
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL("this.extensions.name.has.periods"))),
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL(".thisextensionsnamestartswithaperiod"))),
+ NormalizeSeparators(FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL("thisextensionhasaslashinitsname/")))),
+ };
+ const FilePath expected_crx_names[] = {
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL("this.extensions.name.has.periods.crx"))),
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL(".thisextensionsnamestartswithaperiod.crx"))),
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL("thisextensionhasaslashinitsname.crx"))),
+ };
+ const FilePath expected_private_key_names[] = {
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL("this.extensions.name.has.periods.pem"))),
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL(".thisextensionsnamestartswithaperiod.pem"))),
+ FilePath(FilePath::StringType(
+ FILE_PATH_LITERAL("thisextensionhasaslashinitsname.pem"))),
+ };
+
+ for (size_t i = 0; i < arraysize(punctuated_names); ++i) {
+ SCOPED_TRACE(punctuated_names[i].value().c_str());
+ FilePath output_dir = temp_dir.path().Append(punctuated_names[i]);
+
+ // Copy the extension into the output directory, as PackExtensionJob doesn't
+ // let us choose where to output the packed extension.
+ ASSERT_TRUE(file_util::CopyDirectory(input_directory, output_dir, true));
+
+ FilePath expected_crx_path = temp_dir.path().Append(expected_crx_names[i]);
+ FilePath expected_private_key_path =
+ temp_dir.path().Append(expected_private_key_names[i]);
+ PackExtensionTestClient pack_client(expected_crx_path,
+ expected_private_key_path);
+ scoped_refptr<PackExtensionJob> packer(new PackExtensionJob(&pack_client,
+ output_dir,
+ FilePath()));
+ packer->Start();
+
+ // The packer will post a notification task to the current thread's message
+ // loop when it is finished. We manually run the loop here so that we
+ // block and catch the notification; otherwise, the process would exit.
+ // This call to |Run()| is matched by a call to |Quit()| in the
+ // |PackExtensionTestClient|'s notification handling code.
+ MessageLoop::current()->Run();
+
+ if (HasFatalFailure())
+ return;
+
+ InstallExtension(expected_crx_path, true);
+ }
+}
+
+// Test Packaging and installing an extension using an openssl generated key.
+// The openssl is generated with the following:
+// > openssl genrsa -out privkey.pem 1024
+// > openssl pkcs8 -topk8 -nocrypt -in privkey.pem -out privkey_asn1.pem
+// The privkey.pem is a PrivateKey, and the pcks8 -topk8 creates a
+// PrivateKeyInfo ASN.1 structure, we our RSAPrivateKey expects.
+TEST_F(ExtensionServiceTest, PackExtensionOpenSSLKey) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath input_directory = extensions_path
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+ .AppendASCII("1.0.0.0");
+ FilePath privkey_path(extensions_path.AppendASCII(
+ "openssl_privkey_asn1.pem"));
+ ASSERT_TRUE(file_util::PathExists(privkey_path));
+
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath output_directory = temp_dir.path();
+
+ FilePath crx_path(output_directory.AppendASCII("ex1.crx"));
+
+ scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
+ ASSERT_TRUE(creator->Run(input_directory, crx_path, privkey_path,
+ FilePath()));
+
+ InstallExtension(crx_path, true);
+}
+
+TEST_F(ExtensionServiceTest, InstallTheme) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // A theme.
+ FilePath path = extensions_path.AppendASCII("theme.crx");
+ InstallExtension(path, true);
+ int pref_count = 0;
+ ValidatePrefKeyCount(++pref_count);
+ ValidateIntegerPref(theme_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(theme_crx, "location", Extension::INTERNAL);
+
+ // A theme when extensions are disabled. Themes can be installed, even when
+ // extensions are disabled.
+ set_extensions_enabled(false);
+ path = extensions_path.AppendASCII("theme2.crx");
+ InstallExtension(path, true);
+ ValidatePrefKeyCount(++pref_count);
+ ValidateIntegerPref(theme2_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(theme2_crx, "location", Extension::INTERNAL);
+
+ // A theme with extension elements. Themes cannot have extension elements so
+ // this test should fail.
+ set_extensions_enabled(true);
+ path = extensions_path.AppendASCII("theme_with_extension.crx");
+ InstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
+
+ // A theme with image resources missing (misspelt path).
+ path = extensions_path.AppendASCII("theme_missing_image.crx");
+ InstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
+}
+
+TEST_F(ExtensionServiceTest, LoadLocalizedTheme) {
+ // Load.
+ InitializeEmptyExtensionService();
+ FilePath extension_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extension_path));
+ extension_path = extension_path
+ .AppendASCII("extensions")
+ .AppendASCII("theme_i18n");
+
+ service_->LoadExtension(extension_path);
+ loop_.RunAllPending();
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ("name", service_->extensions()->at(0)->name());
+ EXPECT_EQ("description", service_->extensions()->at(0)->description());
+}
+
+TEST_F(ExtensionServiceTest, InstallLocalizedTheme) {
+ InitializeEmptyExtensionService();
+ FilePath theme_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &theme_path));
+ theme_path = theme_path
+ .AppendASCII("extensions")
+ .AppendASCII("theme_i18n");
+
+ PackAndInstallExtension(theme_path, true);
+
+ EXPECT_EQ(0u, GetErrors().size());
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ("name", service_->extensions()->at(0)->name());
+ EXPECT_EQ("description", service_->extensions()->at(0)->description());
+}
+
+TEST_F(ExtensionServiceTest, InstallApps) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // An empty app.
+ PackAndInstallExtension(extensions_path.AppendASCII("app1"), true);
+ int pref_count = 0;
+ ValidatePrefKeyCount(++pref_count);
+ ASSERT_EQ(1u, service_->extensions()->size());
+ std::string id = service_->extensions()->at(0)->id();
+ ValidateIntegerPref(id, "state", Extension::ENABLED);
+ ValidateIntegerPref(id, "location", Extension::INTERNAL);
+
+ // Another app with non-overlapping extent. Should succeed.
+ PackAndInstallExtension(extensions_path.AppendASCII("app2"), true);
+ ValidatePrefKeyCount(++pref_count);
+
+ // A third app whose extent overlaps the first. Should fail.
+ PackAndInstallExtension(extensions_path.AppendASCII("app3"), false);
+ ValidatePrefKeyCount(pref_count);
+}
+
+TEST_F(ExtensionServiceTest, InstallAppsWithUnlimtedStorage) {
+ InitializeEmptyExtensionService();
+ EXPECT_TRUE(service_->extensions()->empty());
+ EXPECT_TRUE(service_->unlimited_storage_map_.empty());
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ int pref_count = 0;
+ ChromeAppCacheService* appcache_service = profile_->GetAppCacheService();
+
+ // Install app1 with unlimited storage.
+ PackAndInstallExtension(extensions_path.AppendASCII("app1"), true);
+ ValidatePrefKeyCount(++pref_count);
+ ASSERT_EQ(1u, service_->extensions()->size());
+ const Extension* extension = service_->extensions()->at(0);
+ const std::string id1 = extension->id();
+ EXPECT_TRUE(extension->HasApiPermission(
+ Extension::kUnlimitedStoragePermission));
+ EXPECT_TRUE(extension->web_extent().ContainsURL(
+ extension->GetFullLaunchURL()));
+ const GURL origin1(extension->GetFullLaunchURL().GetOrigin());
+ EXPECT_EQ(kint64max,
+ appcache_service->storage()->GetOriginQuotaInMemory(origin1));
+ EXPECT_FALSE(service_->unlimited_storage_map_.empty());
+
+ // Install app2 from the same origin with unlimited storage.
+ PackAndInstallExtension(extensions_path.AppendASCII("app2"), true);
+ ValidatePrefKeyCount(++pref_count);
+ ASSERT_EQ(2u, service_->extensions()->size());
+ extension = service_->extensions()->at(1);
+ const std::string id2 = extension->id();
+ EXPECT_TRUE(extension->HasApiPermission(
+ Extension::kUnlimitedStoragePermission));
+ EXPECT_TRUE(extension->web_extent().ContainsURL(
+ extension->GetFullLaunchURL()));
+ const GURL origin2(extension->GetFullLaunchURL().GetOrigin());
+ EXPECT_EQ(origin1, origin2);
+ EXPECT_EQ(kint64max,
+ appcache_service->storage()->GetOriginQuotaInMemory(origin2));
+ EXPECT_FALSE(service_->unlimited_storage_map_.empty());
+
+ // Uninstall one of them, unlimited storage should still be granted
+ // to the origin.
+ service_->UninstallExtension(id1, false);
+ loop_.RunAllPending();
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ(kint64max,
+ appcache_service->storage()->GetOriginQuotaInMemory(origin1));
+ EXPECT_FALSE(service_->unlimited_storage_map_.empty());
+
+ // Uninstall the other, unlimited storage should be revoked.
+ service_->UninstallExtension(id2, false);
+ loop_.RunAllPending();
+ EXPECT_EQ(0u, service_->extensions()->size());
+ EXPECT_EQ(-1L,
+ appcache_service->storage()->GetOriginQuotaInMemory(origin2));
+ EXPECT_TRUE(service_->unlimited_storage_map_.empty());
+}
+
+TEST_F(ExtensionServiceTest, InstallAppsAndCheckStorageProtection) {
+ InitializeEmptyExtensionService();
+ EXPECT_TRUE(service_->extensions()->empty());
+ EXPECT_TRUE(service_->protected_storage_map_.empty());
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ int pref_count = 0;
+
+ PackAndInstallExtension(extensions_path.AppendASCII("app1"), true);
+ ValidatePrefKeyCount(++pref_count);
+ ASSERT_EQ(1u, service_->extensions()->size());
+ const Extension* extension = service_->extensions()->at(0);
+ EXPECT_TRUE(extension->is_app());
+ const std::string id1 = extension->id();
+ EXPECT_FALSE(service_->protected_storage_map_.empty());
+ const GURL origin1(extension->GetFullLaunchURL().GetOrigin());
+ ASSERT_EQ(1, service_->protected_storage_map_[origin1]);
+
+ // App 4 has a different origin (maps.google.com).
+ PackAndInstallExtension(extensions_path.AppendASCII("app4"), true);
+ ValidatePrefKeyCount(++pref_count);
+ ASSERT_EQ(2u, service_->extensions()->size());
+ extension = service_->extensions()->at(1);
+ const std::string id2 = extension->id();
+ EXPECT_FALSE(service_->protected_storage_map_.empty());
+ const GURL origin2(extension->GetFullLaunchURL().GetOrigin());
+ ASSERT_NE(origin1, origin2);
+ ASSERT_EQ(1, service_->protected_storage_map_[origin2]);
+
+ service_->UninstallExtension(id1, false);
+ loop_.RunAllPending();
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_FALSE(service_->protected_storage_map_.empty());
+
+ service_->UninstallExtension(id2, false);
+ loop_.RunAllPending();
+
+ EXPECT_TRUE(service_->extensions()->empty());
+ EXPECT_TRUE(service_->protected_storage_map_.empty());
+}
+
+// Test that when an extension version is reinstalled, nothing happens.
+TEST_F(ExtensionServiceTest, Reinstall) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // A simple extension that should install without error.
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+
+ ASSERT_TRUE(installed_);
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(0u, GetErrors().size());
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", Extension::INTERNAL);
+
+ installed_ = NULL;
+ loaded_.clear();
+ ExtensionErrorReporter::GetInstance()->ClearErrors();
+
+ // Reinstall the same version, it should overwrite the previous one.
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+
+ ASSERT_TRUE(installed_);
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(0u, GetErrors().size());
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", Extension::INTERNAL);
+}
+
+// Test upgrading a signed extension.
+TEST_F(ExtensionServiceTest, UpgradeSignedGood) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+
+ ASSERT_TRUE(installed_);
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ("1.0.0.0", loaded_[0]->version()->GetString());
+ ASSERT_EQ(0u, GetErrors().size());
+
+ // Upgrade to version 2.0
+ path = extensions_path.AppendASCII("good2.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+
+ ASSERT_TRUE(installed_);
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ("1.0.0.1", loaded_[0]->version()->GetString());
+ ASSERT_EQ(0u, GetErrors().size());
+}
+
+// Test upgrading a signed extension with a bad signature.
+TEST_F(ExtensionServiceTest, UpgradeSignedBad) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+
+ ASSERT_TRUE(installed_);
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(0u, GetErrors().size());
+ installed_ = NULL;
+
+ // Try upgrading with a bad signature. This should fail during the unpack,
+ // because the key will not match the signature.
+ path = extensions_path.AppendASCII("good2_bad_signature.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+
+ ASSERT_FALSE(installed_);
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(1u, GetErrors().size());
+}
+
+// Test a normal update via the UpdateExtension API
+TEST_F(ExtensionServiceTest, UpdateExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+
+ InstallExtension(path, true);
+ const Extension* good = service_->extensions()->at(0);
+ ASSERT_EQ("1.0.0.0", good->VersionString());
+ ASSERT_EQ(good_crx, good->id());
+
+ path = extensions_path.AppendASCII("good2.crx");
+ UpdateExtension(good_crx, path, ENABLED);
+ ASSERT_EQ("1.0.0.1", loaded_[0]->version()->GetString());
+}
+
+// Test updating a not-already-installed extension - this should fail
+TEST_F(ExtensionServiceTest, UpdateNotInstalledExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ UpdateExtension(good_crx, path, UPDATED);
+ loop_.RunAllPending();
+
+ ASSERT_EQ(0u, service_->extensions()->size());
+ ASSERT_FALSE(installed_);
+ ASSERT_EQ(0u, loaded_.size());
+}
+
+// Makes sure you can't downgrade an extension via UpdateExtension
+TEST_F(ExtensionServiceTest, UpdateWillNotDowngrade) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good2.crx");
+
+ InstallExtension(path, true);
+ const Extension* good = service_->extensions()->at(0);
+ ASSERT_EQ("1.0.0.1", good->VersionString());
+ ASSERT_EQ(good_crx, good->id());
+
+ // Change path from good2.crx -> good.crx
+ path = extensions_path.AppendASCII("good.crx");
+ UpdateExtension(good_crx, path, FAILED);
+ ASSERT_EQ("1.0.0.1", service_->extensions()->at(0)->VersionString());
+}
+
+// Make sure calling update with an identical version does nothing
+TEST_F(ExtensionServiceTest, UpdateToSameVersionIsNoop) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+
+ InstallExtension(path, true);
+ const Extension* good = service_->extensions()->at(0);
+ ASSERT_EQ(good_crx, good->id());
+ UpdateExtension(good_crx, path, FAILED_SILENTLY);
+}
+
+// Tests that updating an extension does not clobber old state.
+TEST_F(ExtensionServiceTest, UpdateExtensionPreservesState) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+
+ InstallExtension(path, true);
+ const Extension* good = service_->extensions()->at(0);
+ ASSERT_EQ("1.0.0.0", good->VersionString());
+ ASSERT_EQ(good_crx, good->id());
+
+ // Disable it and allow it to run in incognito. These settings should carry
+ // over to the updated version.
+ service_->DisableExtension(good->id());
+ service_->SetIsIncognitoEnabled(good, true);
+
+ path = extensions_path.AppendASCII("good2.crx");
+ UpdateExtension(good_crx, path, INSTALLED);
+ ASSERT_EQ(1u, service_->disabled_extensions()->size());
+ const Extension* good2 = service_->disabled_extensions()->at(0);
+ ASSERT_EQ("1.0.0.1", good2->version()->GetString());
+ EXPECT_TRUE(service_->IsIncognitoEnabled(good2));
+}
+
+// Tests that updating preserves extension location.
+TEST_F(ExtensionServiceTest, UpdateExtensionPreservesLocation) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+
+ InstallExtension(path, true);
+ const Extension* good = service_->extensions()->at(0);
+
+ ASSERT_EQ("1.0.0.0", good->VersionString());
+ ASSERT_EQ(good_crx, good->id());
+
+ // Simulate non-internal location.
+ const_cast<Extension*>(good)->location_ = Extension::EXTERNAL_PREF;
+
+ path = extensions_path.AppendASCII("good2.crx");
+ UpdateExtension(good_crx, path, ENABLED);
+ const Extension* good2 = service_->extensions()->at(0);
+ ASSERT_EQ("1.0.0.1", good2->version()->GetString());
+ EXPECT_EQ(good2->location(), Extension::EXTERNAL_PREF);
+}
+
+// Makes sure that LOAD extension types can downgrade.
+TEST_F(ExtensionServiceTest, LoadExtensionsCanDowngrade) {
+ InitializeEmptyExtensionService();
+
+ ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ // We'll write the extension manifest dynamically to a temporary path
+ // to make it easier to change the version number.
+ FilePath extension_path = temp.path();
+ FilePath manifest_path = extension_path.Append(Extension::kManifestFilename);
+ ASSERT_FALSE(file_util::PathExists(manifest_path));
+
+ // Start with version 2.0.
+ DictionaryValue manifest;
+ manifest.SetString("version", "2.0");
+ manifest.SetString("name", "LOAD Downgrade Test");
+
+ JSONFileValueSerializer serializer(manifest_path);
+ ASSERT_TRUE(serializer.Serialize(manifest));
+
+ service_->LoadExtension(extension_path);
+ loop_.RunAllPending();
+
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ EXPECT_EQ(Extension::LOAD, loaded_[0]->location());
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ("2.0", loaded_[0]->VersionString());
+
+ // Now set the version number to 1.0, reload the extensions and verify that
+ // the downgrade was accepted.
+ manifest.SetString("version", "1.0");
+ ASSERT_TRUE(serializer.Serialize(manifest));
+
+ service_->LoadExtension(extension_path);
+ loop_.RunAllPending();
+
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ EXPECT_EQ(Extension::LOAD, loaded_[0]->location());
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ("1.0", loaded_[0]->VersionString());
+}
+
+// Test adding a pending extension.
+TEST_F(ExtensionServiceTest, AddPendingExtension) {
+ InitializeEmptyExtensionService();
+
+ const std::string kFakeId("fake-id");
+ const GURL kFakeUpdateURL("http:://fake.update/url");
+ const PendingExtensionInfo::ExpectedCrxType kFakeExpectedCrxType =
+ PendingExtensionInfo::EXTENSION;
+ const bool kFakeInstallSilently(true);
+ const Extension::State kFakeInitialState(Extension::ENABLED);
+ const bool kFakeInitialIncognitoEnabled(false);
+
+ service_->AddPendingExtensionFromSync(
+ kFakeId, kFakeUpdateURL, kFakeExpectedCrxType, kFakeInstallSilently,
+ kFakeInitialState, kFakeInitialIncognitoEnabled);
+ PendingExtensionMap::const_iterator it =
+ service_->pending_extensions().find(kFakeId);
+ ASSERT_TRUE(it != service_->pending_extensions().end());
+ EXPECT_EQ(kFakeUpdateURL, it->second.update_url);
+ EXPECT_EQ(kFakeExpectedCrxType, it->second.expected_crx_type);
+ EXPECT_EQ(kFakeInstallSilently, it->second.install_silently);
+}
+
+namespace {
+const char kGoodId[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
+const char kGoodUpdateURL[] = "http://good.update/url";
+const PendingExtensionInfo::ExpectedCrxType kCrxTypeTheme =
+ PendingExtensionInfo::THEME;
+const PendingExtensionInfo::ExpectedCrxType kCrxTypeExtension =
+ PendingExtensionInfo::EXTENSION;
+const bool kGoodIsFromSync = true;
+const bool kGoodInstallSilently = true;
+const Extension::State kGoodInitialState = Extension::DISABLED;
+const bool kGoodInitialIncognitoEnabled = true;
+} // namespace
+
+// Test updating a pending extension.
+TEST_F(ExtensionServiceTest, UpdatePendingExtension) {
+ InitializeEmptyExtensionService();
+ service_->AddPendingExtensionFromSync(
+ kGoodId, GURL(kGoodUpdateURL), kCrxTypeExtension,
+ kGoodInstallSilently, kGoodInitialState,
+ kGoodInitialIncognitoEnabled);
+ EXPECT_TRUE(ContainsKey(service_->pending_extensions(), kGoodId));
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ UpdateExtension(kGoodId, path, INSTALLED);
+
+ EXPECT_FALSE(ContainsKey(service_->pending_extensions(), kGoodId));
+
+ const Extension* extension = service_->GetExtensionById(kGoodId, true);
+ ASSERT_TRUE(extension);
+
+ bool enabled = service_->GetExtensionById(kGoodId, false);
+ EXPECT_EQ(kGoodInitialState == Extension::ENABLED, enabled);
+ EXPECT_EQ(kGoodInitialState,
+ service_->extension_prefs()->GetExtensionState(extension->id()));
+ EXPECT_EQ(kGoodInitialIncognitoEnabled,
+ service_->IsIncognitoEnabled(extension));
+}
+
+// Test updating a pending theme.
+TEST_F(ExtensionServiceTest, UpdatePendingTheme) {
+ InitializeEmptyExtensionService();
+ service_->AddPendingExtensionFromSync(
+ theme_crx, GURL(), PendingExtensionInfo::THEME,
+ false, Extension::ENABLED, false);
+ EXPECT_TRUE(ContainsKey(service_->pending_extensions(), theme_crx));
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("theme.crx");
+ UpdateExtension(theme_crx, path, ENABLED);
+
+ EXPECT_FALSE(ContainsKey(service_->pending_extensions(), theme_crx));
+
+ const Extension* extension = service_->GetExtensionById(theme_crx, true);
+ ASSERT_TRUE(extension);
+
+ EXPECT_EQ(Extension::ENABLED,
+ service_->extension_prefs()->GetExtensionState(extension->id()));
+ EXPECT_FALSE(service_->IsIncognitoEnabled(extension));
+}
+
+// Test updating a pending CRX as if the source is an external extension
+// with an update URL. In this case we don't know if the CRX is a theme
+// or not.
+TEST_F(ExtensionServiceTest, UpdatePendingExternalCrx) {
+ InitializeEmptyExtensionService();
+ service_->AddPendingExtensionFromExternalUpdateUrl(
+ theme_crx, GURL(), Extension::EXTERNAL_PREF_DOWNLOAD);
+
+ EXPECT_TRUE(ContainsKey(service_->pending_extensions(), theme_crx));
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("theme.crx");
+ UpdateExtension(theme_crx, path, ENABLED);
+
+ EXPECT_FALSE(ContainsKey(service_->pending_extensions(), theme_crx));
+
+ const Extension* extension = service_->GetExtensionById(theme_crx, true);
+ ASSERT_TRUE(extension);
+
+ EXPECT_EQ(Extension::ENABLED,
+ service_->extension_prefs()->GetExtensionState(extension->id()));
+ EXPECT_FALSE(service_->IsIncognitoEnabled(extension));
+}
+
+// Test updating a pending CRX as if the source is an external extension
+// with an update URL. The external update should overwrite a sync update,
+// but a sync update should not overwrite a non-sync update.
+TEST_F(ExtensionServiceTest, UpdatePendingExternalCrxWinsOverSync) {
+ InitializeEmptyExtensionService();
+
+ // Add a crx to be installed from the update mechanism.
+ service_->AddPendingExtensionFromSync(
+ kGoodId, GURL(kGoodUpdateURL), kCrxTypeExtension,
+ kGoodInstallSilently, kGoodInitialState,
+ kGoodInitialIncognitoEnabled);
+
+ // Check that there is a pending crx, with is_from_sync set to true.
+ PendingExtensionMap::const_iterator it;
+ it = service_->pending_extensions().find(kGoodId);
+ ASSERT_TRUE(it != service_->pending_extensions().end());
+ EXPECT_TRUE(it->second.is_from_sync);
+
+ // Add a crx to be updated, with the same ID, from a non-sync source.
+ service_->AddPendingExtensionFromExternalUpdateUrl(
+ kGoodId, GURL(kGoodUpdateURL), Extension::EXTERNAL_PREF_DOWNLOAD);
+
+ // Check that there is a pending crx, with is_from_sync set to false.
+ it = service_->pending_extensions().find(kGoodId);
+ ASSERT_TRUE(it != service_->pending_extensions().end());
+ EXPECT_FALSE(it->second.is_from_sync);
+
+ // Add a crx to be installed from the update mechanism.
+ service_->AddPendingExtensionFromSync(
+ kGoodId, GURL(kGoodUpdateURL), kCrxTypeExtension,
+ kGoodInstallSilently, kGoodInitialState,
+ kGoodInitialIncognitoEnabled);
+
+ // Check that the external, non-sync update was not overridden.
+ it = service_->pending_extensions().find(kGoodId);
+ ASSERT_TRUE(it != service_->pending_extensions().end());
+ EXPECT_FALSE(it->second.is_from_sync);
+}
+
+// Updating a theme should fail if the updater is explicitly told that
+// the CRX is not a theme.
+TEST_F(ExtensionServiceTest, UpdatePendingCrxThemeMismatch) {
+ InitializeEmptyExtensionService();
+ service_->AddPendingExtensionFromSync(
+ theme_crx, GURL(),
+ PendingExtensionInfo::EXTENSION,
+ true, Extension::ENABLED, false);
+
+ EXPECT_TRUE(ContainsKey(service_->pending_extensions(), theme_crx));
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("theme.crx");
+ UpdateExtension(theme_crx, path, FAILED_SILENTLY);
+
+ EXPECT_FALSE(ContainsKey(service_->pending_extensions(), theme_crx));
+
+ const Extension* extension = service_->GetExtensionById(theme_crx, true);
+ ASSERT_FALSE(extension);
+}
+
+// TODO(akalin): Test updating a pending extension non-silently once
+// we can mock out ExtensionInstallUI and inject our version into
+// UpdateExtension().
+
+// Test updating a pending extension with wrong is_theme.
+TEST_F(ExtensionServiceTest, UpdatePendingExtensionWrongIsTheme) {
+ InitializeEmptyExtensionService();
+ // Add pending extension with a flipped is_theme.
+ service_->AddPendingExtensionFromSync(
+ kGoodId, GURL(kGoodUpdateURL),
+ kCrxTypeTheme, kGoodInstallSilently, kGoodInitialState,
+ kGoodInitialIncognitoEnabled);
+ EXPECT_TRUE(ContainsKey(service_->pending_extensions(), kGoodId));
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ UpdateExtension(kGoodId, path, UPDATED);
+
+ // TODO(akalin): Figure out how to check that the extensions
+ // directory is cleaned up properly in OnExtensionInstalled().
+
+ EXPECT_FALSE(ContainsKey(service_->pending_extensions(), kGoodId));
+}
+
+// TODO(akalin): Figure out how to test that installs of pending
+// unsyncable extensions are blocked.
+
+// Test updating a pending extension for one that is not pending.
+TEST_F(ExtensionServiceTest, UpdatePendingExtensionNotPending) {
+ InitializeEmptyExtensionService();
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ UpdateExtension(kGoodId, path, UPDATED);
+
+ EXPECT_FALSE(ContainsKey(service_->pending_extensions(), kGoodId));
+}
+
+// Test updating a pending extension for one that is already
+// installed.
+TEST_F(ExtensionServiceTest, UpdatePendingExtensionAlreadyInstalled) {
+ InitializeEmptyExtensionService();
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+ ASSERT_EQ(1u, service_->extensions()->size());
+ const Extension* good = service_->extensions()->at(0);
+
+ EXPECT_FALSE(good->is_theme());
+
+ // Use AddPendingExtensionInternal() as AddPendingExtension() would
+ // balk.
+ service_->AddPendingExtensionInternal(
+ good->id(), good->update_url(),
+ PendingExtensionInfo::EXTENSION,
+ kGoodIsFromSync, kGoodInstallSilently, kGoodInitialState,
+ kGoodInitialIncognitoEnabled, Extension::INTERNAL);
+ UpdateExtension(good->id(), path, INSTALLED);
+
+ EXPECT_FALSE(ContainsKey(service_->pending_extensions(), kGoodId));
+}
+
+// Test pref settings for blacklist and unblacklist extensions.
+TEST_F(ExtensionServiceTest, SetUnsetBlacklistInPrefs) {
+ InitializeEmptyExtensionService();
+ std::vector<std::string> blacklist;
+ blacklist.push_back(good0);
+ blacklist.push_back("invalid_id"); // an invalid id
+ blacklist.push_back(good1);
+ service_->UpdateExtensionBlacklist(blacklist);
+ // Make sure pref is updated
+ loop_.RunAllPending();
+
+ // blacklist is set for good0,1,2
+ ValidateBooleanPref(good0, "blacklist", true);
+ ValidateBooleanPref(good1, "blacklist", true);
+ // invalid_id should not be inserted to pref.
+ EXPECT_FALSE(IsPrefExist("invalid_id", "blacklist"));
+
+ // remove good1, add good2
+ blacklist.pop_back();
+ blacklist.push_back(good2);
+
+ service_->UpdateExtensionBlacklist(blacklist);
+ // only good0 and good1 should be set
+ ValidateBooleanPref(good0, "blacklist", true);
+ EXPECT_FALSE(IsPrefExist(good1, "blacklist"));
+ ValidateBooleanPref(good2, "blacklist", true);
+ EXPECT_FALSE(IsPrefExist("invalid_id", "blacklist"));
+}
+
+// Unload installed extension from blacklist.
+TEST_F(ExtensionServiceTest, UnloadBlacklistedExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+
+ InstallExtension(path, true);
+ const Extension* good = service_->extensions()->at(0);
+ EXPECT_EQ(good_crx, good->id());
+ UpdateExtension(good_crx, path, FAILED_SILENTLY);
+
+ std::vector<std::string> blacklist;
+ blacklist.push_back(good_crx);
+ service_->UpdateExtensionBlacklist(blacklist);
+ // Make sure pref is updated
+ loop_.RunAllPending();
+
+ // Now, the good_crx is blacklisted.
+ ValidateBooleanPref(good_crx, "blacklist", true);
+ EXPECT_EQ(0u, service_->extensions()->size());
+
+ // Remove good_crx from blacklist
+ blacklist.pop_back();
+ service_->UpdateExtensionBlacklist(blacklist);
+ // Make sure pref is updated
+ loop_.RunAllPending();
+ // blacklist value should not be set for good_crx
+ EXPECT_FALSE(IsPrefExist(good_crx, "blacklist"));
+}
+
+// Unload installed extension from blacklist.
+TEST_F(ExtensionServiceTest, BlacklistedExtensionWillNotInstall) {
+ InitializeEmptyExtensionService();
+ std::vector<std::string> blacklist;
+ blacklist.push_back(good_crx);
+ service_->UpdateExtensionBlacklist(blacklist);
+ // Make sure pref is updated
+ loop_.RunAllPending();
+
+ // Now, the good_crx is blacklisted.
+ ValidateBooleanPref(good_crx, "blacklist", true);
+
+ // We can not install good_crx.
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+ EXPECT_EQ(0u, service_->extensions()->size());
+ ValidateBooleanPref(good_crx, "blacklist", true);
+}
+
+// Test loading extensions from the profile directory, except
+// blacklisted ones.
+TEST_F(ExtensionServiceTest, WillNotLoadBlacklistedExtensionsFromDirectory) {
+ // Initialize the test dir with a good Preferences/extensions.
+ FilePath source_install_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir));
+ source_install_dir = source_install_dir
+ .AppendASCII("extensions")
+ .AppendASCII("good")
+ .AppendASCII("Extensions");
+ FilePath pref_path = source_install_dir
+ .DirName()
+ .AppendASCII("Preferences");
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ // Blacklist good1.
+ std::vector<std::string> blacklist;
+ blacklist.push_back(good1);
+ service_->UpdateExtensionBlacklist(blacklist);
+ // Make sure pref is updated
+ loop_.RunAllPending();
+
+ ValidateBooleanPref(good1, "blacklist", true);
+
+ // Load extensions.
+ service_->Init();
+ loop_.RunAllPending();
+
+ std::vector<std::string> errors = GetErrors();
+ for (std::vector<std::string>::iterator err = errors.begin();
+ err != errors.end(); ++err) {
+ LOG(ERROR) << *err;
+ }
+ ASSERT_EQ(2u, loaded_.size());
+
+ EXPECT_NE(std::string(good1), loaded_[0]->id());
+ EXPECT_NE(std::string(good1), loaded_[1]->id());
+}
+
+#if defined(OS_CHROMEOS)
+// Test loading extensions from the profile directory, except
+// ones with a plugin.
+TEST_F(ExtensionServiceTest, WillNotLoadPluginExtensionsFromDirectory) {
+ // Initialize the test dir with a good Preferences/extensions.
+ FilePath source_install_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir));
+ source_install_dir = source_install_dir
+ .AppendASCII("extensions")
+ .AppendASCII("good")
+ .AppendASCII("Extensions");
+ FilePath pref_path = source_install_dir
+ .DirName()
+ .AppendASCII("Preferences");
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ // good1 contains a plugin.
+ // Load extensions.
+ service_->Init();
+ loop_.RunAllPending();
+
+ std::vector<std::string> errors = GetErrors();
+ for (std::vector<std::string>::iterator err = errors.begin();
+ err != errors.end(); ++err) {
+ LOG(ERROR) << *err;
+ }
+ ASSERT_EQ(2u, loaded_.size());
+
+ EXPECT_NE(std::string(good1), loaded_[0]->id());
+ EXPECT_NE(std::string(good1), loaded_[1]->id());
+}
+#endif
+
+// Will not install extension blacklisted by policy.
+TEST_F(ExtensionServiceTest, BlacklistedByPolicyWillNotInstall) {
+ InitializeEmptyExtensionService();
+
+ ListValue* whitelist =
+ profile_->GetPrefs()->GetMutableList(prefs::kExtensionInstallAllowList);
+ ListValue* blacklist =
+ profile_->GetPrefs()->GetMutableList(prefs::kExtensionInstallDenyList);
+ ASSERT_TRUE(whitelist != NULL && blacklist != NULL);
+
+ // Blacklist everything.
+ blacklist->Append(Value::CreateStringValue("*"));
+
+ // Blacklist prevents us from installing good_crx.
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+ EXPECT_EQ(0u, service_->extensions()->size());
+
+ // Now whitelist this particular extension.
+ whitelist->Append(Value::CreateStringValue(good_crx));
+
+ // Ensure we can now install good_crx.
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+ EXPECT_EQ(1u, service_->extensions()->size());
+}
+
+// Extension blacklisted by policy get unloaded after installing.
+TEST_F(ExtensionServiceTest, BlacklistedByPolicyRemovedIfRunning) {
+ InitializeEmptyExtensionService();
+
+ // Install good_crx.
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ service_->InstallExtension(path);
+ loop_.RunAllPending();
+ EXPECT_EQ(1u, service_->extensions()->size());
+
+ { // Scope for pref update notification.
+ PrefService* prefs = profile_->GetPrefs();
+ ScopedPrefUpdate pref_update(prefs, prefs::kExtensionInstallDenyList);
+ ListValue* blacklist =
+ prefs->GetMutableList(prefs::kExtensionInstallDenyList);
+ ASSERT_TRUE(blacklist != NULL);
+
+ // Blacklist this extension.
+ blacklist->Append(Value::CreateStringValue(good_crx));
+ prefs->ScheduleSavePersistentPrefs();
+ }
+
+ // Extension should not be running now.
+ loop_.RunAllPending();
+ EXPECT_EQ(0u, service_->extensions()->size());
+}
+
+// Tests disabling extensions
+TEST_F(ExtensionServiceTest, DisableExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // A simple extension that should install without error.
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+
+ const char* extension_id = good_crx;
+ EXPECT_FALSE(service_->extensions()->empty());
+ EXPECT_TRUE(service_->GetExtensionById(extension_id, true) != NULL);
+ EXPECT_TRUE(service_->GetExtensionById(extension_id, false) != NULL);
+ EXPECT_TRUE(service_->disabled_extensions()->empty());
+
+ // Disable it.
+ service_->DisableExtension(extension_id);
+
+ EXPECT_TRUE(service_->extensions()->empty());
+ EXPECT_TRUE(service_->GetExtensionById(extension_id, true) != NULL);
+ EXPECT_FALSE(service_->GetExtensionById(extension_id, false) != NULL);
+ EXPECT_FALSE(service_->disabled_extensions()->empty());
+}
+
+// Tests disabling all extensions (simulating --disable-extensions flag).
+TEST_F(ExtensionServiceTest, DisableAllExtensions) {
+ InitializeEmptyExtensionService();
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ(0u, service_->disabled_extensions()->size());
+
+ // Disable extensions.
+ service_->set_extensions_enabled(false);
+ service_->ReloadExtensions();
+
+ // There shouldn't be extensions in either list.
+ EXPECT_EQ(0u, service_->extensions()->size());
+ EXPECT_EQ(0u, service_->disabled_extensions()->size());
+
+ // This shouldn't do anything when all extensions are disabled.
+ service_->EnableExtension(good_crx);
+ service_->ReloadExtensions();
+
+ // There still shouldn't be extensions in either list.
+ EXPECT_EQ(0u, service_->extensions()->size());
+ EXPECT_EQ(0u, service_->disabled_extensions()->size());
+
+ // And then re-enable the extensions.
+ service_->set_extensions_enabled(true);
+ service_->ReloadExtensions();
+
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ(0u, service_->disabled_extensions()->size());
+}
+
+// Tests reloading extensions
+TEST_F(ExtensionServiceTest, ReloadExtensions) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // Simple extension that should install without error.
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+ const char* extension_id = good_crx;
+ service_->DisableExtension(extension_id);
+
+ EXPECT_EQ(0u, service_->extensions()->size());
+ EXPECT_EQ(1u, service_->disabled_extensions()->size());
+
+ service_->ReloadExtensions();
+
+ // Extension counts shouldn't change.
+ EXPECT_EQ(0u, service_->extensions()->size());
+ EXPECT_EQ(1u, service_->disabled_extensions()->size());
+
+ service_->EnableExtension(extension_id);
+
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ(0u, service_->disabled_extensions()->size());
+
+ // Need to clear |loaded_| manually before reloading as the
+ // EnableExtension() call above inserted into it and
+ // UnloadAllExtensions() doesn't send out notifications.
+ loaded_.clear();
+ service_->ReloadExtensions();
+
+ // Extension counts shouldn't change.
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ(0u, service_->disabled_extensions()->size());
+}
+
+// Tests uninstalling normal extensions
+TEST_F(ExtensionServiceTest, UninstallExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // A simple extension that should install without error.
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+
+ // The directory should be there now.
+ const char* extension_id = good_crx;
+ FilePath extension_path = extensions_install_dir_.AppendASCII(extension_id);
+ EXPECT_TRUE(file_util::PathExists(extension_path));
+
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", Extension::INTERNAL);
+
+ // Uninstall it.
+ service_->UninstallExtension(extension_id, false);
+ total_successes_ = 0;
+
+ // We should get an unload notification.
+ ASSERT_TRUE(unloaded_id_.length());
+ EXPECT_EQ(extension_id, unloaded_id_);
+
+ ValidatePrefKeyCount(0);
+
+ // The extension should not be in the service anymore.
+ ASSERT_FALSE(service_->GetExtensionById(extension_id, false));
+ loop_.RunAllPending();
+
+ // The directory should be gone.
+ EXPECT_FALSE(file_util::PathExists(extension_path));
+}
+
+// Tests the uninstaller helper.
+TEST_F(ExtensionServiceTest, UninstallExtensionHelper) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ // A simple extension that should install without error.
+ FilePath path = extensions_path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+
+ // The directory should be there now.
+ const char* extension_id = good_crx;
+ FilePath extension_path = extensions_install_dir_.AppendASCII(extension_id);
+ EXPECT_TRUE(file_util::PathExists(extension_path));
+
+ bool result = ExtensionService::UninstallExtensionHelper(service_,
+ extension_id);
+ total_successes_ = 0;
+
+ EXPECT_TRUE(result);
+
+ // We should get an unload notification.
+ ASSERT_TRUE(unloaded_id_.length());
+ EXPECT_EQ(extension_id, unloaded_id_);
+
+ ValidatePrefKeyCount(0);
+
+ // The extension should not be in the service anymore.
+ ASSERT_FALSE(service_->GetExtensionById(extension_id, false));
+ loop_.RunAllPending();
+
+ // The directory should be gone.
+ EXPECT_FALSE(file_util::PathExists(extension_path));
+
+ // Attempt to uninstall again. This should fail as we just removed the
+ // extension.
+ result = ExtensionService::UninstallExtensionHelper(service_, extension_id);
+ EXPECT_FALSE(result);
+}
+
+// Verifies extension state is removed upon uninstall
+TEST_F(ExtensionServiceTest, ClearExtensionData) {
+ InitializeEmptyExtensionService();
+
+ // Load a test extension.
+ FilePath path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions");
+ path = path.AppendASCII("good.crx");
+ InstallExtension(path, true);
+ const Extension* extension = service_->GetExtensionById(good_crx, false);
+ ASSERT_TRUE(extension);
+ GURL ext_url(extension->url());
+ string16 origin_id =
+ webkit_database::DatabaseUtil::GetOriginIdentifier(ext_url);
+
+ // Set a cookie for the extension.
+ net::CookieMonster* cookie_monster = profile_
+ ->GetRequestContextForExtensions()->GetCookieStore()->GetCookieMonster();
+ ASSERT_TRUE(cookie_monster);
+ net::CookieOptions options;
+ cookie_monster->SetCookieWithOptions(ext_url, "dummy=value", options);
+ net::CookieList list = cookie_monster->GetAllCookiesForURL(ext_url);
+ EXPECT_EQ(1U, list.size());
+
+ // Open a database.
+ webkit_database::DatabaseTracker* db_tracker = profile_->GetDatabaseTracker();
+ string16 db_name = UTF8ToUTF16("db");
+ string16 description = UTF8ToUTF16("db_description");
+ int64 size;
+ int64 available;
+ db_tracker->DatabaseOpened(origin_id, db_name, description, 1, &size,
+ &available);
+ db_tracker->DatabaseClosed(origin_id, db_name);
+ std::vector<webkit_database::OriginInfo> origins;
+ db_tracker->GetAllOriginsInfo(&origins);
+ EXPECT_EQ(1U, origins.size());
+ EXPECT_EQ(origin_id, origins[0].GetOrigin());
+
+ // Create local storage. We only simulate this by creating the backing file
+ // since webkit is not initialized.
+ DOMStorageContext* context =
+ profile_->GetWebKitContext()->dom_storage_context();
+ FilePath lso_path = context->GetLocalStorageFilePath(origin_id);
+ EXPECT_TRUE(file_util::CreateDirectory(lso_path.DirName()));
+ EXPECT_EQ(0, file_util::WriteFile(lso_path, NULL, 0));
+ EXPECT_TRUE(file_util::PathExists(lso_path));
+
+ // Create indexed db. Again, it is enough to only simulate this by creating
+ // the file on the disk.
+ IndexedDBContext* idb_context =
+ profile_->GetWebKitContext()->indexed_db_context();
+ FilePath idb_path = idb_context->GetIndexedDBFilePath(origin_id);
+ EXPECT_TRUE(file_util::CreateDirectory(idb_path.DirName()));
+ EXPECT_EQ(0, file_util::WriteFile(idb_path, NULL, 0));
+ EXPECT_TRUE(file_util::PathExists(idb_path));
+
+ // Uninstall the extension.
+ service_->UninstallExtension(good_crx, false);
+ loop_.RunAllPending();
+
+ // Check that the cookie is gone.
+ list = cookie_monster->GetAllCookiesForURL(ext_url);
+ EXPECT_EQ(0U, list.size());
+
+ // The database should have vanished as well.
+ origins.clear();
+ db_tracker->GetAllOriginsInfo(&origins);
+ EXPECT_EQ(0U, origins.size());
+
+ // Check that the LSO file has been removed.
+ EXPECT_FALSE(file_util::PathExists(lso_path));
+
+ // Check if the indexed db has disappeared too.
+ EXPECT_FALSE(file_util::PathExists(idb_path));
+}
+
+// Tests loading single extensions (like --load-extension)
+TEST_F(ExtensionServiceTest, LoadExtension) {
+ InitializeEmptyExtensionService();
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath ext1 = extensions_path
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+ .AppendASCII("1.0.0.0");
+ service_->LoadExtension(ext1);
+ loop_.RunAllPending();
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ EXPECT_EQ(Extension::LOAD, loaded_[0]->location());
+ EXPECT_EQ(1u, service_->extensions()->size());
+
+ ValidatePrefKeyCount(1);
+
+ FilePath no_manifest = extensions_path
+ .AppendASCII("bad")
+ // .AppendASCII("Extensions")
+ .AppendASCII("cccccccccccccccccccccccccccccccc")
+ .AppendASCII("1");
+ service_->LoadExtension(no_manifest);
+ loop_.RunAllPending();
+ EXPECT_EQ(1u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ EXPECT_EQ(1u, service_->extensions()->size());
+
+ // Test uninstall.
+ std::string id = loaded_[0]->id();
+ EXPECT_FALSE(unloaded_id_.length());
+ service_->UninstallExtension(id, false);
+ loop_.RunAllPending();
+ EXPECT_EQ(id, unloaded_id_);
+ ASSERT_EQ(0u, loaded_.size());
+ EXPECT_EQ(0u, service_->extensions()->size());
+}
+
+// Tests that we generate IDs when they are not specified in the manifest for
+// --load-extension.
+TEST_F(ExtensionServiceTest, GenerateID) {
+ InitializeEmptyExtensionService();
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+
+ FilePath no_id_ext = extensions_path.AppendASCII("no_id");
+ service_->LoadExtension(no_id_ext);
+ loop_.RunAllPending();
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_TRUE(Extension::IdIsValid(loaded_[0]->id()));
+ EXPECT_EQ(loaded_[0]->location(), Extension::LOAD);
+
+ ValidatePrefKeyCount(1);
+
+ std::string previous_id = loaded_[0]->id();
+
+ // If we reload the same path, we should get the same extension ID.
+ service_->LoadExtension(no_id_ext);
+ loop_.RunAllPending();
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(previous_id, loaded_[0]->id());
+}
+
+void ExtensionServiceTest::TestExternalProvider(
+ MockExtensionProvider* provider, Extension::Location location) {
+ // Verify that starting with no providers loads no extensions.
+ service_->Init();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, loaded_.size());
+
+ provider->set_visit_count(0);
+
+ // Register a test extension externally using the mock registry provider.
+ FilePath source_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_path));
+ source_path = source_path.AppendASCII("extensions").AppendASCII("good.crx");
+
+ // Add the extension.
+ provider->UpdateOrAddExtension(good_crx, "1.0.0.0", source_path);
+
+ // Reloading extensions should find our externally registered extension
+ // and install it.
+ service_->CheckForExternalUpdates();
+ loop_.RunAllPending();
+
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(location, loaded_[0]->location());
+ ASSERT_EQ("1.0.0.0", loaded_[0]->version()->GetString());
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", location);
+
+ // Reload extensions without changing anything. The extension should be
+ // loaded again.
+ loaded_.clear();
+ service_->ReloadExtensions();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", location);
+
+ // Now update the extension with a new version. We should get upgraded.
+ source_path = source_path.DirName().AppendASCII("good2.crx");
+ provider->UpdateOrAddExtension(good_crx, "1.0.0.1", source_path);
+
+ loaded_.clear();
+ service_->CheckForExternalUpdates();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ("1.0.0.1", loaded_[0]->version()->GetString());
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", location);
+
+ // Uninstall the extension and reload. Nothing should happen because the
+ // preference should prevent us from reinstalling.
+ std::string id = loaded_[0]->id();
+ service_->UninstallExtension(id, false);
+ loop_.RunAllPending();
+
+ // The extension should also be gone from the install directory.
+ FilePath install_path = extensions_install_dir_.AppendASCII(id);
+ ASSERT_FALSE(file_util::PathExists(install_path));
+
+ loaded_.clear();
+ service_->CheckForExternalUpdates();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, loaded_.size());
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::KILLBIT);
+ ValidateIntegerPref(good_crx, "location", location);
+
+ // Now clear the preference and reinstall.
+ SetPrefInteg(good_crx, "state", Extension::ENABLED);
+ profile_->GetPrefs()->ScheduleSavePersistentPrefs();
+
+ loaded_.clear();
+ service_->CheckForExternalUpdates();
+ loop_.RunAllPending();
+ ASSERT_EQ(1u, loaded_.size());
+ ValidatePrefKeyCount(1);
+ ValidateIntegerPref(good_crx, "state", Extension::ENABLED);
+ ValidateIntegerPref(good_crx, "location", location);
+
+ // Now test an externally triggered uninstall (deleting the registry key or
+ // the pref entry).
+ provider->RemoveExtension(good_crx);
+
+ loaded_.clear();
+ service_->UnloadAllExtensions();
+ service_->LoadAllExtensions();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, loaded_.size());
+ ValidatePrefKeyCount(0);
+
+ // The extension should also be gone from the install directory.
+ ASSERT_FALSE(file_util::PathExists(install_path));
+
+ // Now test the case where user uninstalls and then the extension is removed
+ // from the external provider.
+
+ provider->UpdateOrAddExtension(good_crx, "1.0", source_path);
+ service_->CheckForExternalUpdates();
+ loop_.RunAllPending();
+
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(0u, GetErrors().size());
+
+ // User uninstalls.
+ loaded_.clear();
+ service_->UninstallExtension(id, false);
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, loaded_.size());
+
+ // Then remove the extension from the extension provider.
+ provider->RemoveExtension(good_crx);
+
+ // Should still be at 0.
+ loaded_.clear();
+ service_->LoadAllExtensions();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, loaded_.size());
+ ValidatePrefKeyCount(1);
+
+ EXPECT_EQ(5, provider->visit_count());
+}
+
+// Tests the external installation feature
+#if defined(OS_WIN)
+TEST_F(ExtensionServiceTest, ExternalInstallRegistry) {
+ // This should all work, even when normal extension installation is disabled.
+ InitializeEmptyExtensionService();
+ set_extensions_enabled(false);
+
+ // Now add providers. Extension system takes ownership of the objects.
+ MockExtensionProvider* reg_provider =
+ new MockExtensionProvider(Extension::EXTERNAL_REGISTRY);
+ AddMockExternalProvider(reg_provider);
+ TestExternalProvider(reg_provider, Extension::EXTERNAL_REGISTRY);
+}
+#endif
+
+TEST_F(ExtensionServiceTest, ExternalInstallPref) {
+ InitializeEmptyExtensionService();
+
+ // Now add providers. Extension system takes ownership of the objects.
+ MockExtensionProvider* pref_provider =
+ new MockExtensionProvider(Extension::EXTERNAL_PREF);
+
+ AddMockExternalProvider(pref_provider);
+ TestExternalProvider(pref_provider, Extension::EXTERNAL_PREF);
+}
+
+TEST_F(ExtensionServiceTest, ExternalInstallPrefUpdateUrl) {
+ // This should all work, even when normal extension installation is disabled.
+ InitializeEmptyExtensionService();
+ set_extensions_enabled(false);
+
+ // TODO(skerner): The mock provider is not a good model of a provider
+ // that works with update URLs, because it adds file and version info.
+ // Extend the mock to work with update URLs. This test checks the
+ // behavior that is common to all external extension visitors. The
+ // browser test ExtensionManagementTest.ExternalUrlUpdate tests that
+ // what the visitor does results in an extension being downloaded and
+ // installed.
+ MockExtensionProvider* pref_provider =
+ new MockExtensionProvider(Extension::EXTERNAL_PREF_DOWNLOAD);
+ AddMockExternalProvider(pref_provider);
+ TestExternalProvider(pref_provider, Extension::EXTERNAL_PREF_DOWNLOAD);
+}
+
+// Tests that external extensions get uninstalled when the external extension
+// providers can't account for them.
+TEST_F(ExtensionServiceTest, ExternalUninstall) {
+ // Start the extensions service with one external extension already installed.
+ FilePath source_install_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir));
+ source_install_dir = source_install_dir
+ .AppendASCII("extensions")
+ .AppendASCII("good")
+ .AppendASCII("Extensions");
+ FilePath pref_path = source_install_dir
+ .DirName()
+ .AppendASCII("PreferencesExternal");
+
+ // This initializes the extensions service with no ExternalExtensionProviders.
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+ set_extensions_enabled(false);
+
+ service_->Init();
+ loop_.RunAllPending();
+
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(0u, loaded_.size());
+
+ // Verify that it's not the disabled extensions flag causing it not to load.
+ set_extensions_enabled(true);
+ service_->ReloadExtensions();
+ loop_.RunAllPending();
+
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(0u, loaded_.size());
+}
+
+TEST_F(ExtensionServiceTest, ExternalPrefProvider) {
+ InitializeEmptyExtensionService();
+ std::string json_data =
+ "{"
+ " \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\": {"
+ " \"external_crx\": \"RandomExtension.crx\","
+ " \"external_version\": \"1.0\""
+ " },"
+ " \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\": {"
+ " \"external_crx\": \"RandomExtension2.crx\","
+ " \"external_version\": \"2.0\""
+ " },"
+ " \"cccccccccccccccccccccccccccccccc\": {"
+ " \"external_update_url\": \"http:\\\\foo.com/update\""
+ " }"
+ "}";
+
+ MockProviderVisitor visitor;
+
+ // Simulate an external_extensions.json file that contains seven invalid
+ // extensions:
+ // - One that is missing the 'external_crx' key.
+ // - One that is missing the 'external_version' key.
+ // - One that is specifying .. in the path.
+ // - One that specifies both a file and update URL.
+ // - One that specifies no file or update URL.
+ // - One that has an update URL that is not well formed.
+ // - One that contains a malformed version.
+ // The final extension is valid, and we check that it is read to make sure
+ // failures don't stop valid records from being read.
+ json_data =
+ "{"
+ " \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\": {"
+ " \"external_version\": \"1.0\""
+ " },"
+ " \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\": {"
+ " \"external_crx\": \"RandomExtension.crx\""
+ " },"
+ " \"cccccccccccccccccccccccccccccccc\": {"
+ " \"external_crx\": \"..\\\\foo\\\\RandomExtension2.crx\","
+ " \"external_version\": \"2.0\""
+ " },"
+ " \"dddddddddddddddddddddddddddddddd\": {"
+ " \"external_crx\": \"RandomExtension2.crx\","
+ " \"external_version\": \"2.0\","
+ " \"external_update_url\": \"http:\\\\foo.com/update\""
+ " },"
+ " \"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\": {"
+ " },"
+ " \"ffffffffffffffffffffffffffffffff\": {"
+ " \"external_update_url\": \"This string is not a valid URL\""
+ " },"
+ " \"gggggggggggggggggggggggggggggggg\": {"
+ " \"external_crx\": \"RandomExtension3.crx\","
+ " \"external_version\": \"This is not a valid version!\""
+ " },"
+ " \"hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh\": {"
+ " \"external_crx\": \"RandomValidExtension.crx\","
+ " \"external_version\": \"1.0\""
+ " }"
+ "}";
+ EXPECT_EQ(1, visitor.Visit(json_data));
+}
+
+// Test loading good extensions from the profile directory.
+TEST_F(ExtensionServiceTest, LoadAndRelocalizeExtensions) {
+ // Initialize the test dir with a good Preferences/extensions.
+ FilePath source_install_dir;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir));
+ source_install_dir = source_install_dir
+ .AppendASCII("extensions")
+ .AppendASCII("l10n");
+ FilePath pref_path = source_install_dir.AppendASCII("Preferences");
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ service_->Init();
+ loop_.RunAllPending();
+
+ ASSERT_EQ(3u, loaded_.size());
+
+ // This was equal to "sr" on load.
+ ValidateStringPref(loaded_[0]->id(), keys::kCurrentLocale, "en");
+
+ // These are untouched by re-localization.
+ ValidateStringPref(loaded_[1]->id(), keys::kCurrentLocale, "en");
+ EXPECT_FALSE(IsPrefExist(loaded_[1]->id(), keys::kCurrentLocale));
+
+ // This one starts with Serbian name, and gets re-localized into English.
+ EXPECT_EQ("My name is simple.", loaded_[0]->name());
+
+ // These are untouched by re-localization.
+ EXPECT_EQ("My name is simple.", loaded_[1]->name());
+ EXPECT_EQ("no l10n", loaded_[2]->name());
+}
+
+class ExtensionsReadyRecorder : public NotificationObserver {
+ public:
+ ExtensionsReadyRecorder() : ready_(false) {
+ registrar_.Add(this, NotificationType::EXTENSIONS_READY,
+ NotificationService::AllSources());
+ }
+
+ void set_ready(bool value) { ready_ = value; }
+ bool ready() { return ready_; }
+
+ private:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::EXTENSIONS_READY:
+ ready_ = true;
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ NotificationRegistrar registrar_;
+ bool ready_;
+};
+
+// Test that we get enabled/disabled correctly for all the pref/command-line
+// combinations. We don't want to derive from the ExtensionServiceTest class
+// for this test, so we use ExtensionServiceTestSimple.
+//
+// Also tests that we always fire EXTENSIONS_READY, no matter whether we are
+// enabled or not.
+TEST(ExtensionServiceTestSimple, Enabledness) {
+ ExtensionsReadyRecorder recorder;
+ scoped_ptr<TestingProfile> profile(new TestingProfile());
+ MessageLoop loop;
+ BrowserThread ui_thread(BrowserThread::UI, &loop);
+ BrowserThread file_thread(BrowserThread::FILE, &loop);
+ scoped_ptr<CommandLine> command_line;
+ scoped_refptr<ExtensionService> service;
+ FilePath install_dir = profile->GetPath()
+ .AppendASCII(ExtensionService::kInstallDirectoryName);
+
+ // By default, we are enabled.
+ command_line.reset(new CommandLine(CommandLine::NO_PROGRAM));
+ service = profile->CreateExtensionService(command_line.get(),
+ install_dir);
+ EXPECT_TRUE(service->extensions_enabled());
+ service->Init();
+ loop.RunAllPending();
+ EXPECT_TRUE(recorder.ready());
+
+ // If either the command line or pref is set, we are disabled.
+ recorder.set_ready(false);
+ profile.reset(new TestingProfile());
+ command_line->AppendSwitch(switches::kDisableExtensions);
+ service = profile->CreateExtensionService(command_line.get(),
+ install_dir);
+ EXPECT_FALSE(service->extensions_enabled());
+ service->Init();
+ loop.RunAllPending();
+ EXPECT_TRUE(recorder.ready());
+
+ recorder.set_ready(false);
+ profile.reset(new TestingProfile());
+ profile->GetPrefs()->SetBoolean(prefs::kDisableExtensions, true);
+ service = profile->CreateExtensionService(command_line.get(),
+ install_dir);
+ EXPECT_FALSE(service->extensions_enabled());
+ service->Init();
+ loop.RunAllPending();
+ EXPECT_TRUE(recorder.ready());
+
+ recorder.set_ready(false);
+ profile.reset(new TestingProfile());
+ profile->GetPrefs()->SetBoolean(prefs::kDisableExtensions, true);
+ command_line.reset(new CommandLine(CommandLine::NO_PROGRAM));
+ service = profile->CreateExtensionService(command_line.get(),
+ install_dir);
+ EXPECT_FALSE(service->extensions_enabled());
+ service->Init();
+ loop.RunAllPending();
+ EXPECT_TRUE(recorder.ready());
+
+ // Explicitly delete all the resources used in this test.
+ profile.reset();
+ service = NULL;
+}
+
+// Test loading extensions that require limited and unlimited storage quotas.
+TEST_F(ExtensionServiceTest, StorageQuota) {
+ InitializeEmptyExtensionService();
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions")
+ .AppendASCII("storage_quota");
+
+ FilePath limited_quota_ext = extensions_path.AppendASCII("limited_quota")
+ .AppendASCII("1.0");
+
+ // The old permission name for unlimited quota was "unlimited_storage", but
+ // we changed it to "unlimitedStorage". This tests both versions.
+ FilePath unlimited_quota_ext = extensions_path.AppendASCII("unlimited_quota")
+ .AppendASCII("1.0");
+ FilePath unlimited_quota_ext2 = extensions_path.AppendASCII("unlimited_quota")
+ .AppendASCII("2.0");
+ service_->LoadExtension(limited_quota_ext);
+ service_->LoadExtension(unlimited_quota_ext);
+ service_->LoadExtension(unlimited_quota_ext2);
+ loop_.RunAllPending();
+
+ ASSERT_EQ(3u, loaded_.size());
+ EXPECT_TRUE(profile_.get());
+ EXPECT_FALSE(profile_->IsOffTheRecord());
+
+ // Open the database from each origin to make the tracker aware
+ // of the existence of these origins and to get their quotas.
+ int64 limited_quota = -1;
+ int64 unlimited_quota = -1;
+ string16 limited_quota_identifier =
+ webkit_database::DatabaseUtil::GetOriginIdentifier(loaded_[0]->url());
+ string16 unlimited_quota_identifier =
+ webkit_database::DatabaseUtil::GetOriginIdentifier(loaded_[1]->url());
+ string16 unlimited_quota_identifier2 =
+ webkit_database::DatabaseUtil::GetOriginIdentifier(loaded_[2]->url());
+ string16 db_name = UTF8ToUTF16("db");
+ string16 description = UTF8ToUTF16("db_description");
+ int64 database_size;
+ webkit_database::DatabaseTracker* db_tracker = profile_->GetDatabaseTracker();
+
+ // First check the normal limited quota extension.
+ db_tracker->DatabaseOpened(limited_quota_identifier, db_name, description,
+ 1, &database_size, &limited_quota);
+ db_tracker->DatabaseClosed(limited_quota_identifier, db_name);
+ EXPECT_EQ(profile_->GetDatabaseTracker()->GetDefaultQuota(), limited_quota);
+
+ // Now check the two unlimited quota ones.
+ db_tracker->DatabaseOpened(unlimited_quota_identifier, db_name, description,
+ 1, &database_size, &unlimited_quota);
+ db_tracker->DatabaseClosed(unlimited_quota_identifier, db_name);
+ EXPECT_EQ(kint64max, unlimited_quota);
+ db_tracker->DatabaseOpened(unlimited_quota_identifier2, db_name, description,
+ 1, &database_size, &unlimited_quota);
+ db_tracker->DatabaseClosed(unlimited_quota_identifier2, db_name);
+
+ EXPECT_EQ(kint64max, unlimited_quota);
+}
+
+// Tests ExtensionService::register_component_extension().
+TEST_F(ExtensionServiceTest, ComponentExtensions) {
+ InitializeEmptyExtensionService();
+
+ // Component extensions should work even when extensions are disabled.
+ set_extensions_enabled(false);
+
+ FilePath path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions")
+ .AppendASCII("good")
+ .AppendASCII("Extensions")
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
+ .AppendASCII("1.0.0.0");
+
+ std::string manifest;
+ ASSERT_TRUE(file_util::ReadFileToString(
+ path.Append(Extension::kManifestFilename), &manifest));
+
+ service_->register_component_extension(
+ ExtensionService::ComponentExtensionInfo(manifest, path));
+ service_->Init();
+
+ // Note that we do not pump messages -- the extension should be loaded
+ // immediately.
+
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ EXPECT_EQ(Extension::COMPONENT, loaded_[0]->location());
+ EXPECT_EQ(1u, service_->extensions()->size());
+
+ // Component extensions shouldn't get recourded in the prefs.
+ ValidatePrefKeyCount(0);
+
+ // Reload all extensions, and make sure it comes back.
+ std::string extension_id = service_->extensions()->at(0)->id();
+ loaded_.clear();
+ service_->ReloadExtensions();
+ ASSERT_EQ(1u, service_->extensions()->size());
+ EXPECT_EQ(extension_id, service_->extensions()->at(0)->id());
+}