blob: 4120d39e22ccf47524931a21f0e27308f8d081f0 [file] [log] [blame]
// Copyright 2014 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/shared_module_service.h"
#include <memory>
#include <utility>
#include "base/memory/ref_counted.h"
#include "base/strings/string16.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/pending_extension_manager.h"
#include "components/crx_file/id_util.h"
#include "components/sync/model/string_ordinal.h"
#include "components/version_info/version_info.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/install_flag.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/value_builder.h"
namespace extensions {
namespace {
// Return an extension with |id| which imports all the modules that are in the
// container |import_ids|.
scoped_refptr<const Extension> CreateExtensionImportingModules(
const std::vector<std::string>& import_ids,
const std::string& id,
const std::string& version) {
DictionaryBuilder builder;
builder.Set("name", "Has Dependent Modules")
.Set("version", version)
.Set("manifest_version", 2);
if (!import_ids.empty()) {
ListBuilder import_list;
for (const std::string& id : import_ids)
import_list.Append(DictionaryBuilder().Set("id", id).Build());
builder.Set("import", import_list.Build());
}
return ExtensionBuilder()
.SetManifest(builder.Build())
.AddFlags(Extension::FROM_WEBSTORE)
.SetID(id)
.Build();
}
scoped_refptr<const Extension> CreateSharedModule(
const std::string& module_id) {
std::unique_ptr<base::DictionaryValue> manifest =
DictionaryBuilder()
.Set("name", "Shared Module")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("export",
DictionaryBuilder()
.Set("resources", ListBuilder().Append("foo.js").Build())
.Build())
.Build();
return ExtensionBuilder()
.SetManifest(std::move(manifest))
.AddFlags(Extension::FROM_WEBSTORE)
.SetID(crx_file::id_util::GenerateId(module_id))
.Build();
}
} // namespace
class SharedModuleServiceUnitTest : public ExtensionServiceTestBase {
public:
SharedModuleServiceUnitTest() :
// The "export" key is open for dev-channel only, but unit tests
// run as stable channel on the official Windows build.
current_channel_(version_info::Channel::UNKNOWN) {}
protected:
void SetUp() override;
// Install an extension and notify the ExtensionService.
testing::AssertionResult InstallExtension(const Extension* extension,
bool is_update);
ScopedCurrentChannel current_channel_;
};
void SharedModuleServiceUnitTest::SetUp() {
ExtensionServiceTestBase::SetUp();
InitializeGoodInstalledExtensionService();
service()->Init();
}
testing::AssertionResult SharedModuleServiceUnitTest::InstallExtension(
const Extension* extension,
bool is_update) {
const Extension* old = registry()->GetExtensionById(
extension->id(),
ExtensionRegistry::ENABLED);
// Verify the extension is not already installed, if it is not update.
if (!is_update) {
if (old)
return testing::AssertionFailure() << "Extension already installed.";
} else {
if (!old)
return testing::AssertionFailure() << "The extension does not exist.";
}
// Notify the service that the extension is installed. This adds it to the
// registry, notifies interested parties, etc.
service()->OnExtensionInstalled(
extension, syncer::StringOrdinal(), kInstallFlagInstallImmediately);
// Verify that the extension is now installed.
if (!registry()->GetExtensionById(extension->id(),
ExtensionRegistry::ENABLED)) {
return testing::AssertionFailure() << "Could not install extension.";
}
return testing::AssertionSuccess();
}
TEST_F(SharedModuleServiceUnitTest, AddDependentSharedModules) {
// Create an extension that has a dependency.
std::string import_id = crx_file::id_util::GenerateId("id");
std::string extension_id = crx_file::id_util::GenerateId("extension_id");
scoped_refptr<const Extension> extension = CreateExtensionImportingModules(
std::vector<std::string>(1, import_id), extension_id, "1.0");
PendingExtensionManager* pending_extension_manager =
service()->pending_extension_manager();
// Verify that we don't currently want to install the imported module.
EXPECT_FALSE(pending_extension_manager->IsIdPending(import_id));
// Try to satisfy imports for the extension. This should queue the imported
// module's installation.
service()->shared_module_service()->SatisfyImports(extension.get());
EXPECT_TRUE(pending_extension_manager->IsIdPending(import_id));
}
TEST_F(SharedModuleServiceUnitTest, PruneSharedModulesOnUninstall) {
// Create a module which exports a resource, and install it.
scoped_refptr<const Extension> shared_module =
CreateSharedModule("shared_module");
EXPECT_TRUE(InstallExtension(shared_module.get(), false));
std::string extension_id = crx_file::id_util::GenerateId("extension_id");
// Create and install an extension that imports our new module.
scoped_refptr<const Extension> importing_extension =
CreateExtensionImportingModules(
std::vector<std::string>(1, shared_module->id()), extension_id,
"1.0");
EXPECT_TRUE(InstallExtension(importing_extension.get(), false));
// Uninstall the extension that imports our module.
base::string16 error;
service()->UninstallExtension(importing_extension->id(),
extensions::UNINSTALL_REASON_FOR_TESTING,
&error);
EXPECT_TRUE(error.empty());
// Since the module was only referenced by that single extension, it should
// have been uninstalled as a side-effect of uninstalling the extension that
// depended upon it.
EXPECT_FALSE(registry()->GetExtensionById(shared_module->id(),
ExtensionRegistry::EVERYTHING));
}
TEST_F(SharedModuleServiceUnitTest, PruneSharedModulesOnUpdate) {
// Create two modules which export a resource, and install them.
scoped_refptr<const Extension> shared_module_1 =
CreateSharedModule("shared_module_1");
EXPECT_TRUE(InstallExtension(shared_module_1.get(), false));
std::unique_ptr<base::DictionaryValue> manifest_2 =
DictionaryBuilder()
.Set("name", "Shared Module 2")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("export",
DictionaryBuilder()
.Set("resources", ListBuilder().Append("foo.js").Build())
.Build())
.Build();
scoped_refptr<const Extension> shared_module_2 =
CreateSharedModule("shared_module_2");
EXPECT_TRUE(InstallExtension(shared_module_2.get(), false));
std::string extension_id = crx_file::id_util::GenerateId("extension_id");
// Create and install an extension v1.0 that imports our new module 1.
scoped_refptr<const Extension> importing_extension_1 =
CreateExtensionImportingModules(
std::vector<std::string>(1, shared_module_1->id()), extension_id,
"1.0");
EXPECT_TRUE(InstallExtension(importing_extension_1.get(), false));
// Create and install a new version of the extension that imports our new
// module 2.
scoped_refptr<const Extension> importing_extension_2 =
CreateExtensionImportingModules(
std::vector<std::string>(1, shared_module_2->id()), extension_id,
"1.1");
EXPECT_TRUE(InstallExtension(importing_extension_2.get(), true));
// Since the extension v1.1 depends the module 2 insteand module 1.
// So the module 1 should be uninstalled.
EXPECT_FALSE(registry()->GetExtensionById(shared_module_1->id(),
ExtensionRegistry::EVERYTHING));
EXPECT_TRUE(registry()->GetExtensionById(shared_module_2->id(),
ExtensionRegistry::EVERYTHING));
// Create and install a new version of the extension that does not import any
// module.
scoped_refptr<const Extension> importing_extension_3 =
CreateExtensionImportingModules(std::vector<std::string>(), extension_id,
"1.2");
EXPECT_TRUE(InstallExtension(importing_extension_3.get(), true));
// Since the extension v1.2 does not depend any module, so the all models
// should have been uninstalled.
EXPECT_FALSE(registry()->GetExtensionById(shared_module_1->id(),
ExtensionRegistry::EVERYTHING));
EXPECT_FALSE(registry()->GetExtensionById(shared_module_2->id(),
ExtensionRegistry::EVERYTHING));
}
TEST_F(SharedModuleServiceUnitTest, WhitelistedImports) {
std::string whitelisted_id = crx_file::id_util::GenerateId("whitelisted");
std::string nonwhitelisted_id =
crx_file::id_util::GenerateId("nonwhitelisted");
// Create a module which exports to a restricted whitelist.
std::unique_ptr<base::DictionaryValue> manifest =
DictionaryBuilder()
.Set("name", "Shared Module")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("export",
DictionaryBuilder()
.Set("whitelist",
ListBuilder().Append(whitelisted_id).Build())
.Set("resources", ListBuilder().Append("*").Build())
.Build())
.Build();
scoped_refptr<const Extension> shared_module =
ExtensionBuilder()
.SetManifest(std::move(manifest))
.AddFlags(Extension::FROM_WEBSTORE)
.SetID(crx_file::id_util::GenerateId("shared_module"))
.Build();
EXPECT_TRUE(InstallExtension(shared_module.get(), false));
// Create and install an extension with the whitelisted ID.
scoped_refptr<const Extension> whitelisted_extension =
CreateExtensionImportingModules(
std::vector<std::string>(1, shared_module->id()), whitelisted_id,
"1.0");
EXPECT_TRUE(InstallExtension(whitelisted_extension.get(), false));
// Try to install an extension with an ID that is not whitelisted.
scoped_refptr<const Extension> nonwhitelisted_extension =
CreateExtensionImportingModules(
std::vector<std::string>(1, shared_module->id()), nonwhitelisted_id,
"1.0");
// This should succeed because only CRX installer (and by extension the
// WebStore Installer) checks the shared module whitelist. InstallExtension
// bypasses the whitelist check because the SharedModuleService does not
// care about whitelists.
EXPECT_TRUE(InstallExtension(nonwhitelisted_extension.get(), false));
}
TEST_F(SharedModuleServiceUnitTest, PruneMultipleSharedModules) {
// Create two modules which export a resource each, and install it.
scoped_refptr<const Extension> shared_module_one =
CreateSharedModule("shared_module_one");
EXPECT_TRUE(InstallExtension(shared_module_one.get(), false));
scoped_refptr<const Extension> shared_module_two =
CreateSharedModule("shared_module_two");
EXPECT_TRUE(InstallExtension(shared_module_two.get(), false));
std::string extension_id = crx_file::id_util::GenerateId("extension_id");
std::vector<std::string> module_ids;
module_ids.push_back(shared_module_one->id());
module_ids.push_back(shared_module_two->id());
// Create and install an extension that imports both the modules.
scoped_refptr<const Extension> importing_extension =
CreateExtensionImportingModules(module_ids, extension_id, "1.0");
EXPECT_TRUE(InstallExtension(importing_extension.get(), false));
// Uninstall the extension that imports our modules.
base::string16 error;
service()->UninstallExtension(importing_extension->id(),
extensions::UNINSTALL_REASON_FOR_TESTING,
&error);
EXPECT_TRUE(error.empty());
// Since the modules were only referenced by that single extension, they
// should have been uninstalled as a side-effect of uninstalling the extension
// that depended upon it.
EXPECT_FALSE(registry()->GetExtensionById(shared_module_one->id(),
ExtensionRegistry::EVERYTHING));
EXPECT_FALSE(registry()->GetExtensionById(shared_module_two->id(),
ExtensionRegistry::EVERYTHING));
}
} // namespace extensions