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 "base/memory/ref_counted.h"
6 #include "base/memory/scoped_ptr.h"
7 #include "base/strings/string16.h"
8 #include "base/values.h"
9 #include "chrome/browser/extensions/extension_service.h"
10 #include "chrome/browser/extensions/extension_service_test_base.h"
11 #include "chrome/browser/extensions/pending_extension_manager.h"
12 #include "chrome/browser/extensions/shared_module_service.h"
13 #include "chrome/common/extensions/features/feature_channel.h"
14 #include "components/crx_file/id_util.h"
15 #include "extensions/browser/extension_registry.h"
16 #include "extensions/browser/install_flag.h"
17 #include "extensions/browser/uninstall_reason.h"
18 #include "extensions/common/extension_builder.h"
19 #include "extensions/common/value_builder.h"
20 #include "sync/api/string_ordinal.h"
22 namespace extensions
{
26 // Return an extension with |id| which imports a module with the given
28 scoped_refptr
<Extension
> CreateExtensionImportingModule(
29 const std::string
& import_id
,
30 const std::string
& id
,
31 const std::string
& version
) {
32 DictionaryBuilder builder
;
33 builder
.Set("name", "Has Dependent Modules")
34 .Set("version", version
)
35 .Set("manifest_version", 2);
36 if (!import_id
.empty()) {
38 ListBuilder().Append(DictionaryBuilder().Set("id", import_id
)));
40 scoped_ptr
<base::DictionaryValue
> manifest
= builder
.Build();
42 return ExtensionBuilder().SetManifest(manifest
.Pass())
43 .AddFlags(Extension::FROM_WEBSTORE
)
50 class SharedModuleServiceUnitTest
: public ExtensionServiceTestBase
{
52 SharedModuleServiceUnitTest() :
53 // The "export" key is open for dev-channel only, but unit tests
54 // run as stable channel on the official Windows build.
55 current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN
) {}
57 void SetUp() override
;
59 // Install an extension and notify the ExtensionService.
60 testing::AssertionResult
InstallExtension(const Extension
* extension
,
62 ScopedCurrentChannel current_channel_
;
65 void SharedModuleServiceUnitTest::SetUp() {
66 ExtensionServiceTestBase::SetUp();
67 InitializeGoodInstalledExtensionService();
71 testing::AssertionResult
SharedModuleServiceUnitTest::InstallExtension(
72 const Extension
* extension
,
75 const Extension
* old
= registry()->GetExtensionById(
77 ExtensionRegistry::ENABLED
);
79 // Verify the extension is not already installed, if it is not update.
82 return testing::AssertionFailure() << "Extension already installed.";
85 return testing::AssertionFailure() << "The extension does not exist.";
88 // Notify the service that the extension is installed. This adds it to the
89 // registry, notifies interested parties, etc.
90 service()->OnExtensionInstalled(
91 extension
, syncer::StringOrdinal(), kInstallFlagInstallImmediately
);
93 // Verify that the extension is now installed.
94 if (!registry()->GetExtensionById(extension
->id(),
95 ExtensionRegistry::ENABLED
)) {
96 return testing::AssertionFailure() << "Could not install extension.";
99 return testing::AssertionSuccess();
102 TEST_F(SharedModuleServiceUnitTest
, AddDependentSharedModules
) {
103 // Create an extension that has a dependency.
104 std::string import_id
= crx_file::id_util::GenerateId("id");
105 std::string extension_id
= crx_file::id_util::GenerateId("extension_id");
106 scoped_refptr
<Extension
> extension
=
107 CreateExtensionImportingModule(import_id
, extension_id
, "1.0");
109 PendingExtensionManager
* pending_extension_manager
=
110 service()->pending_extension_manager();
112 // Verify that we don't currently want to install the imported module.
113 EXPECT_FALSE(pending_extension_manager
->IsIdPending(import_id
));
115 // Try to satisfy imports for the extension. This should queue the imported
116 // module's installation.
117 service()->shared_module_service()->SatisfyImports(extension
.get());
118 EXPECT_TRUE(pending_extension_manager
->IsIdPending(import_id
));
121 TEST_F(SharedModuleServiceUnitTest
, PruneSharedModulesOnUninstall
) {
122 // Create a module which exports a resource, and install it.
123 scoped_ptr
<base::DictionaryValue
> manifest
=
125 .Set("name", "Shared Module")
126 .Set("version", "1.0")
127 .Set("manifest_version", 2)
129 DictionaryBuilder().Set("resources",
130 ListBuilder().Append("foo.js"))).Build();
131 scoped_refptr
<Extension
> shared_module
=
133 .SetManifest(manifest
.Pass())
134 .AddFlags(Extension::FROM_WEBSTORE
)
135 .SetID(crx_file::id_util::GenerateId("shared_module"))
138 EXPECT_TRUE(InstallExtension(shared_module
.get(), false));
140 std::string extension_id
= crx_file::id_util::GenerateId("extension_id");
141 // Create and install an extension that imports our new module.
142 scoped_refptr
<Extension
> importing_extension
=
143 CreateExtensionImportingModule(shared_module
->id(), extension_id
, "1.0");
144 EXPECT_TRUE(InstallExtension(importing_extension
.get(), false));
146 // Uninstall the extension that imports our module.
147 base::string16 error
;
148 service()->UninstallExtension(importing_extension
->id(),
149 extensions::UNINSTALL_REASON_FOR_TESTING
,
150 base::Bind(&base::DoNothing
),
152 EXPECT_TRUE(error
.empty());
154 // Since the module was only referenced by that single extension, it should
155 // have been uninstalled as a side-effect of uninstalling the extension that
157 EXPECT_FALSE(registry()->GetExtensionById(shared_module
->id(),
158 ExtensionRegistry::EVERYTHING
));
161 TEST_F(SharedModuleServiceUnitTest
, PruneSharedModulesOnUpdate
) {
162 // Create two modules which export a resource, and install them.
163 scoped_ptr
<base::DictionaryValue
> manifest_1
=
165 .Set("name", "Shared Module 1")
166 .Set("version", "1.0")
167 .Set("manifest_version", 2)
169 DictionaryBuilder().Set("resources",
170 ListBuilder().Append("foo.js"))).Build();
171 scoped_refptr
<Extension
> shared_module_1
=
173 .SetManifest(manifest_1
.Pass())
174 .AddFlags(Extension::FROM_WEBSTORE
)
175 .SetID(crx_file::id_util::GenerateId("shared_module_1"))
177 EXPECT_TRUE(InstallExtension(shared_module_1
.get(), false));
179 scoped_ptr
<base::DictionaryValue
> manifest_2
=
181 .Set("name", "Shared Module 2")
182 .Set("version", "1.0")
183 .Set("manifest_version", 2)
185 DictionaryBuilder().Set("resources",
186 ListBuilder().Append("foo.js"))).Build();
187 scoped_refptr
<Extension
> shared_module_2
=
189 .SetManifest(manifest_2
.Pass())
190 .AddFlags(Extension::FROM_WEBSTORE
)
191 .SetID(crx_file::id_util::GenerateId("shared_module_2"))
193 EXPECT_TRUE(InstallExtension(shared_module_2
.get(), false));
195 std::string extension_id
= crx_file::id_util::GenerateId("extension_id");
197 // Create and install an extension v1.0 that imports our new module 1.
198 scoped_refptr
<Extension
> importing_extension_1
=
199 CreateExtensionImportingModule(shared_module_1
->id(),
202 EXPECT_TRUE(InstallExtension(importing_extension_1
.get(), false));
204 // Create and install a new version of the extension that imports our new
206 scoped_refptr
<Extension
> importing_extension_2
=
207 CreateExtensionImportingModule(shared_module_2
->id(),
210 EXPECT_TRUE(InstallExtension(importing_extension_2
.get(), true));
212 // Since the extension v1.1 depends the module 2 insteand module 1.
213 // So the module 1 should be uninstalled.
214 EXPECT_FALSE(registry()->GetExtensionById(shared_module_1
->id(),
215 ExtensionRegistry::EVERYTHING
));
216 EXPECT_TRUE(registry()->GetExtensionById(shared_module_2
->id(),
217 ExtensionRegistry::EVERYTHING
));
219 // Create and install a new version of the extension that does not import any
221 scoped_refptr
<Extension
> importing_extension_3
=
222 CreateExtensionImportingModule("", extension_id
, "1.2");
223 EXPECT_TRUE(InstallExtension(importing_extension_3
.get(), true));
225 // Since the extension v1.2 does not depend any module, so the all models
226 // should have been uninstalled.
227 EXPECT_FALSE(registry()->GetExtensionById(shared_module_1
->id(),
228 ExtensionRegistry::EVERYTHING
));
229 EXPECT_FALSE(registry()->GetExtensionById(shared_module_2
->id(),
230 ExtensionRegistry::EVERYTHING
));
234 TEST_F(SharedModuleServiceUnitTest
, WhitelistedImports
) {
235 std::string whitelisted_id
= crx_file::id_util::GenerateId("whitelisted");
236 std::string nonwhitelisted_id
=
237 crx_file::id_util::GenerateId("nonwhitelisted");
238 // Create a module which exports to a restricted whitelist.
239 scoped_ptr
<base::DictionaryValue
> manifest
=
241 .Set("name", "Shared Module")
242 .Set("version", "1.0")
243 .Set("manifest_version", 2)
245 DictionaryBuilder().Set("whitelist",
247 .Append(whitelisted_id
))
249 ListBuilder().Append("*"))).Build();
250 scoped_refptr
<Extension
> shared_module
=
252 .SetManifest(manifest
.Pass())
253 .AddFlags(Extension::FROM_WEBSTORE
)
254 .SetID(crx_file::id_util::GenerateId("shared_module"))
257 EXPECT_TRUE(InstallExtension(shared_module
.get(), false));
259 // Create and install an extension with the whitelisted ID.
260 scoped_refptr
<Extension
> whitelisted_extension
=
261 CreateExtensionImportingModule(shared_module
->id(),
264 EXPECT_TRUE(InstallExtension(whitelisted_extension
.get(), false));
266 // Try to install an extension with an ID that is not whitelisted.
267 scoped_refptr
<Extension
> nonwhitelisted_extension
=
268 CreateExtensionImportingModule(shared_module
->id(),
271 // This should succeed because only CRX installer (and by extension the
272 // WebStore Installer) checks the shared module whitelist. InstallExtension
273 // bypasses the whitelist check because the SharedModuleService does not
274 // care about whitelists.
275 EXPECT_TRUE(InstallExtension(nonwhitelisted_extension
.get(), false));
278 } // namespace extensions