1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/shared_module_service.h"
7 #include "base/memory/ref_counted.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/strings/string16.h"
10 #include "base/values.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/extensions/extension_service_test_base.h"
13 #include "chrome/browser/extensions/pending_extension_manager.h"
14 #include "chrome/common/extensions/features/feature_channel.h"
15 #include "components/crx_file/id_util.h"
16 #include "components/version_info/version_info.h"
17 #include "extensions/browser/extension_registry.h"
18 #include "extensions/browser/install_flag.h"
19 #include "extensions/browser/uninstall_reason.h"
20 #include "extensions/common/extension_builder.h"
21 #include "extensions/common/value_builder.h"
22 #include "sync/api/string_ordinal.h"
24 namespace extensions
{
28 // Return an extension with |id| which imports a module with the given
30 scoped_refptr
<Extension
> CreateExtensionImportingModule(
31 const std::string
& import_id
,
32 const std::string
& id
,
33 const std::string
& version
) {
34 DictionaryBuilder builder
;
35 builder
.Set("name", "Has Dependent Modules")
36 .Set("version", version
)
37 .Set("manifest_version", 2);
38 if (!import_id
.empty()) {
40 ListBuilder().Append(DictionaryBuilder().Set("id", import_id
)));
42 scoped_ptr
<base::DictionaryValue
> manifest
= builder
.Build();
44 return ExtensionBuilder().SetManifest(manifest
.Pass())
45 .AddFlags(Extension::FROM_WEBSTORE
)
52 class SharedModuleServiceUnitTest
: public ExtensionServiceTestBase
{
54 SharedModuleServiceUnitTest() :
55 // The "export" key is open for dev-channel only, but unit tests
56 // run as stable channel on the official Windows build.
57 current_channel_(version_info::Channel::UNKNOWN
) {}
59 void SetUp() override
;
61 // Install an extension and notify the ExtensionService.
62 testing::AssertionResult
InstallExtension(const Extension
* extension
,
64 ScopedCurrentChannel current_channel_
;
67 void SharedModuleServiceUnitTest::SetUp() {
68 ExtensionServiceTestBase::SetUp();
69 InitializeGoodInstalledExtensionService();
73 testing::AssertionResult
SharedModuleServiceUnitTest::InstallExtension(
74 const Extension
* extension
,
77 const Extension
* old
= registry()->GetExtensionById(
79 ExtensionRegistry::ENABLED
);
81 // Verify the extension is not already installed, if it is not update.
84 return testing::AssertionFailure() << "Extension already installed.";
87 return testing::AssertionFailure() << "The extension does not exist.";
90 // Notify the service that the extension is installed. This adds it to the
91 // registry, notifies interested parties, etc.
92 service()->OnExtensionInstalled(
93 extension
, syncer::StringOrdinal(), kInstallFlagInstallImmediately
);
95 // Verify that the extension is now installed.
96 if (!registry()->GetExtensionById(extension
->id(),
97 ExtensionRegistry::ENABLED
)) {
98 return testing::AssertionFailure() << "Could not install extension.";
101 return testing::AssertionSuccess();
104 TEST_F(SharedModuleServiceUnitTest
, AddDependentSharedModules
) {
105 // Create an extension that has a dependency.
106 std::string import_id
= crx_file::id_util::GenerateId("id");
107 std::string extension_id
= crx_file::id_util::GenerateId("extension_id");
108 scoped_refptr
<Extension
> extension
=
109 CreateExtensionImportingModule(import_id
, extension_id
, "1.0");
111 PendingExtensionManager
* pending_extension_manager
=
112 service()->pending_extension_manager();
114 // Verify that we don't currently want to install the imported module.
115 EXPECT_FALSE(pending_extension_manager
->IsIdPending(import_id
));
117 // Try to satisfy imports for the extension. This should queue the imported
118 // module's installation.
119 service()->shared_module_service()->SatisfyImports(extension
.get());
120 EXPECT_TRUE(pending_extension_manager
->IsIdPending(import_id
));
123 TEST_F(SharedModuleServiceUnitTest
, PruneSharedModulesOnUninstall
) {
124 // Create a module which exports a resource, and install it.
125 scoped_ptr
<base::DictionaryValue
> manifest
=
127 .Set("name", "Shared Module")
128 .Set("version", "1.0")
129 .Set("manifest_version", 2)
131 DictionaryBuilder().Set("resources",
132 ListBuilder().Append("foo.js"))).Build();
133 scoped_refptr
<Extension
> shared_module
=
135 .SetManifest(manifest
.Pass())
136 .AddFlags(Extension::FROM_WEBSTORE
)
137 .SetID(crx_file::id_util::GenerateId("shared_module"))
140 EXPECT_TRUE(InstallExtension(shared_module
.get(), false));
142 std::string extension_id
= crx_file::id_util::GenerateId("extension_id");
143 // Create and install an extension that imports our new module.
144 scoped_refptr
<Extension
> importing_extension
=
145 CreateExtensionImportingModule(shared_module
->id(), extension_id
, "1.0");
146 EXPECT_TRUE(InstallExtension(importing_extension
.get(), false));
148 // Uninstall the extension that imports our module.
149 base::string16 error
;
150 service()->UninstallExtension(importing_extension
->id(),
151 extensions::UNINSTALL_REASON_FOR_TESTING
,
152 base::Bind(&base::DoNothing
),
154 EXPECT_TRUE(error
.empty());
156 // Since the module was only referenced by that single extension, it should
157 // have been uninstalled as a side-effect of uninstalling the extension that
159 EXPECT_FALSE(registry()->GetExtensionById(shared_module
->id(),
160 ExtensionRegistry::EVERYTHING
));
163 TEST_F(SharedModuleServiceUnitTest
, PruneSharedModulesOnUpdate
) {
164 // Create two modules which export a resource, and install them.
165 scoped_ptr
<base::DictionaryValue
> manifest_1
=
167 .Set("name", "Shared Module 1")
168 .Set("version", "1.0")
169 .Set("manifest_version", 2)
171 DictionaryBuilder().Set("resources",
172 ListBuilder().Append("foo.js"))).Build();
173 scoped_refptr
<Extension
> shared_module_1
=
175 .SetManifest(manifest_1
.Pass())
176 .AddFlags(Extension::FROM_WEBSTORE
)
177 .SetID(crx_file::id_util::GenerateId("shared_module_1"))
179 EXPECT_TRUE(InstallExtension(shared_module_1
.get(), false));
181 scoped_ptr
<base::DictionaryValue
> manifest_2
=
183 .Set("name", "Shared Module 2")
184 .Set("version", "1.0")
185 .Set("manifest_version", 2)
187 DictionaryBuilder().Set("resources",
188 ListBuilder().Append("foo.js"))).Build();
189 scoped_refptr
<Extension
> shared_module_2
=
191 .SetManifest(manifest_2
.Pass())
192 .AddFlags(Extension::FROM_WEBSTORE
)
193 .SetID(crx_file::id_util::GenerateId("shared_module_2"))
195 EXPECT_TRUE(InstallExtension(shared_module_2
.get(), false));
197 std::string extension_id
= crx_file::id_util::GenerateId("extension_id");
199 // Create and install an extension v1.0 that imports our new module 1.
200 scoped_refptr
<Extension
> importing_extension_1
=
201 CreateExtensionImportingModule(shared_module_1
->id(),
204 EXPECT_TRUE(InstallExtension(importing_extension_1
.get(), false));
206 // Create and install a new version of the extension that imports our new
208 scoped_refptr
<Extension
> importing_extension_2
=
209 CreateExtensionImportingModule(shared_module_2
->id(),
212 EXPECT_TRUE(InstallExtension(importing_extension_2
.get(), true));
214 // Since the extension v1.1 depends the module 2 insteand module 1.
215 // So the module 1 should be uninstalled.
216 EXPECT_FALSE(registry()->GetExtensionById(shared_module_1
->id(),
217 ExtensionRegistry::EVERYTHING
));
218 EXPECT_TRUE(registry()->GetExtensionById(shared_module_2
->id(),
219 ExtensionRegistry::EVERYTHING
));
221 // Create and install a new version of the extension that does not import any
223 scoped_refptr
<Extension
> importing_extension_3
=
224 CreateExtensionImportingModule("", extension_id
, "1.2");
225 EXPECT_TRUE(InstallExtension(importing_extension_3
.get(), true));
227 // Since the extension v1.2 does not depend any module, so the all models
228 // should have been uninstalled.
229 EXPECT_FALSE(registry()->GetExtensionById(shared_module_1
->id(),
230 ExtensionRegistry::EVERYTHING
));
231 EXPECT_FALSE(registry()->GetExtensionById(shared_module_2
->id(),
232 ExtensionRegistry::EVERYTHING
));
236 TEST_F(SharedModuleServiceUnitTest
, WhitelistedImports
) {
237 std::string whitelisted_id
= crx_file::id_util::GenerateId("whitelisted");
238 std::string nonwhitelisted_id
=
239 crx_file::id_util::GenerateId("nonwhitelisted");
240 // Create a module which exports to a restricted whitelist.
241 scoped_ptr
<base::DictionaryValue
> manifest
=
243 .Set("name", "Shared Module")
244 .Set("version", "1.0")
245 .Set("manifest_version", 2)
247 DictionaryBuilder().Set("whitelist",
249 .Append(whitelisted_id
))
251 ListBuilder().Append("*"))).Build();
252 scoped_refptr
<Extension
> shared_module
=
254 .SetManifest(manifest
.Pass())
255 .AddFlags(Extension::FROM_WEBSTORE
)
256 .SetID(crx_file::id_util::GenerateId("shared_module"))
259 EXPECT_TRUE(InstallExtension(shared_module
.get(), false));
261 // Create and install an extension with the whitelisted ID.
262 scoped_refptr
<Extension
> whitelisted_extension
=
263 CreateExtensionImportingModule(shared_module
->id(),
266 EXPECT_TRUE(InstallExtension(whitelisted_extension
.get(), false));
268 // Try to install an extension with an ID that is not whitelisted.
269 scoped_refptr
<Extension
> nonwhitelisted_extension
=
270 CreateExtensionImportingModule(shared_module
->id(),
273 // This should succeed because only CRX installer (and by extension the
274 // WebStore Installer) checks the shared module whitelist. InstallExtension
275 // bypasses the whitelist check because the SharedModuleService does not
276 // care about whitelists.
277 EXPECT_TRUE(InstallExtension(nonwhitelisted_extension
.get(), false));
280 } // namespace extensions