| // 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 "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.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 "chrome/browser/extensions/shared_module_service.h" |
| #include "chrome/common/extensions/features/feature_channel.h" |
| #include "components/crx_file/id_util.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/value_builder.h" |
| #include "sync/api/string_ordinal.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // Return an extension with |id| which imports a module with the given |
| // |import_id|. |
| scoped_refptr<Extension> CreateExtensionImportingModule( |
| const std::string& import_id, |
| 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_id.empty()) { |
| builder.Set("import", |
| ListBuilder().Append(DictionaryBuilder().Set("id", import_id))); |
| } |
| scoped_ptr<base::DictionaryValue> manifest = builder.Build(); |
| |
| return ExtensionBuilder().SetManifest(manifest.Pass()) |
| .AddFlags(Extension::FROM_WEBSTORE) |
| .SetID(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_(chrome::VersionInfo::CHANNEL_UNKNOWN) {} |
| protected: |
| virtual 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<Extension> extension = |
| CreateExtensionImportingModule(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_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(); |
| scoped_refptr<Extension> shared_module = |
| ExtensionBuilder() |
| .SetManifest(manifest.Pass()) |
| .AddFlags(Extension::FROM_WEBSTORE) |
| .SetID(crx_file::id_util::GenerateId("shared_module")) |
| .Build(); |
| |
| 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<Extension> importing_extension = |
| CreateExtensionImportingModule(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, |
| base::Bind(&base::DoNothing), |
| &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_ptr<base::DictionaryValue> manifest_1 = |
| DictionaryBuilder() |
| .Set("name", "Shared Module 1") |
| .Set("version", "1.0") |
| .Set("manifest_version", 2) |
| .Set("export", |
| DictionaryBuilder().Set("resources", |
| ListBuilder().Append("foo.js"))).Build(); |
| scoped_refptr<Extension> shared_module_1 = |
| ExtensionBuilder() |
| .SetManifest(manifest_1.Pass()) |
| .AddFlags(Extension::FROM_WEBSTORE) |
| .SetID(crx_file::id_util::GenerateId("shared_module_1")) |
| .Build(); |
| EXPECT_TRUE(InstallExtension(shared_module_1.get(), false)); |
| |
| scoped_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(); |
| scoped_refptr<Extension> shared_module_2 = |
| ExtensionBuilder() |
| .SetManifest(manifest_2.Pass()) |
| .AddFlags(Extension::FROM_WEBSTORE) |
| .SetID(crx_file::id_util::GenerateId("shared_module_2")) |
| .Build(); |
| 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<Extension> importing_extension_1 = |
| CreateExtensionImportingModule(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<Extension> importing_extension_2 = |
| CreateExtensionImportingModule(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<Extension> importing_extension_3 = |
| CreateExtensionImportingModule("", 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. |
| scoped_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)) |
| .Set("resources", |
| ListBuilder().Append("*"))).Build(); |
| scoped_refptr<Extension> shared_module = |
| ExtensionBuilder() |
| .SetManifest(manifest.Pass()) |
| .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<Extension> whitelisted_extension = |
| CreateExtensionImportingModule(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<Extension> nonwhitelisted_extension = |
| CreateExtensionImportingModule(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)); |
| } |
| |
| } // namespace extensions |