1 // Copyright (c) 2012 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/sync/test/integration/sync_extension_helper.h"
7 #include "base/files/file_path.h"
8 #include "base/files/file_util.h"
10 #include "base/logging.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/values.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/extensions/pending_extension_info.h"
17 #include "chrome/browser/extensions/pending_extension_manager.h"
18 #include "chrome/browser/extensions/signin/gaia_auth_extension_loader.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
21 #include "chrome/browser/sync/test/integration/sync_test.h"
22 #include "components/crx_file/id_util.h"
23 #include "extensions/browser/extension_prefs.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/browser/extension_system.h"
26 #include "extensions/browser/install_flag.h"
27 #include "extensions/browser/uninstall_reason.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_set.h"
30 #include "extensions/common/manifest_constants.h"
31 #include "sync/api/string_ordinal.h"
32 #include "testing/gtest/include/gtest/gtest.h"
34 using extensions::Extension
;
35 using extensions::ExtensionPrefs
;
36 using extensions::ExtensionRegistry
;
37 using extensions::Manifest
;
39 const char kFakeExtensionPrefix
[] = "fakeextension";
41 SyncExtensionHelper::ExtensionState::ExtensionState()
42 : enabled_state(ENABLED
), disable_reasons(0), incognito_enabled(false) {}
44 SyncExtensionHelper::ExtensionState::~ExtensionState() {}
46 bool SyncExtensionHelper::ExtensionState::Equals(
47 const SyncExtensionHelper::ExtensionState
&other
) const {
48 return ((enabled_state
== other
.enabled_state
) &&
49 (disable_reasons
== other
.disable_reasons
) &&
50 (incognito_enabled
== other
.incognito_enabled
));
54 SyncExtensionHelper
* SyncExtensionHelper::GetInstance() {
55 SyncExtensionHelper
* instance
= Singleton
<SyncExtensionHelper
>::get();
56 instance
->SetupIfNecessary(sync_datatype_helper::test());
60 SyncExtensionHelper::SyncExtensionHelper() : setup_completed_(false) {}
62 SyncExtensionHelper::~SyncExtensionHelper() {}
64 void SyncExtensionHelper::SetupIfNecessary(SyncTest
* test
) {
68 extension_name_prefix_
= kFakeExtensionPrefix
+ base::GenerateGUID();
69 for (int i
= 0; i
< test
->num_clients(); ++i
) {
70 SetupProfile(test
->GetProfile(i
));
72 if (test
->use_verifier()) {
73 SetupProfile(test
->verifier());
76 setup_completed_
= true;
79 std::string
SyncExtensionHelper::InstallExtension(
80 Profile
* profile
, const std::string
& name
, Manifest::Type type
) {
81 scoped_refptr
<Extension
> extension
= GetExtension(profile
, name
, type
);
82 if (!extension
.get()) {
83 NOTREACHED() << "Could not install extension " << name
;
86 extensions::ExtensionSystem::Get(profile
)
88 ->OnExtensionInstalled(extension
.get(),
89 syncer::StringOrdinal(),
90 extensions::kInstallFlagInstallImmediately
);
91 return extension
->id();
94 void SyncExtensionHelper::UninstallExtension(
95 Profile
* profile
, const std::string
& name
) {
96 ExtensionService::UninstallExtensionHelper(
97 extensions::ExtensionSystem::Get(profile
)->extension_service(),
98 crx_file::id_util::GenerateId(name
),
99 extensions::UNINSTALL_REASON_SYNC
);
102 std::vector
<std::string
> SyncExtensionHelper::GetInstalledExtensionNames(
103 Profile
* profile
) const {
104 std::vector
<std::string
> names
;
106 scoped_ptr
<const extensions::ExtensionSet
> extensions(
107 extensions::ExtensionRegistry::Get(profile
)
108 ->GenerateInstalledExtensionsSet());
109 for (extensions::ExtensionSet::const_iterator it
= extensions
->begin();
110 it
!= extensions
->end(); ++it
) {
111 names
.push_back((*it
)->name());
117 void SyncExtensionHelper::EnableExtension(Profile
* profile
,
118 const std::string
& name
) {
119 extensions::ExtensionSystem::Get(profile
)
120 ->extension_service()
121 ->EnableExtension(crx_file::id_util::GenerateId(name
));
124 void SyncExtensionHelper::DisableExtension(Profile
* profile
,
125 const std::string
& name
) {
126 extensions::ExtensionSystem::Get(profile
)
127 ->extension_service()
128 ->DisableExtension(crx_file::id_util::GenerateId(name
),
129 Extension::DISABLE_USER_ACTION
);
132 bool SyncExtensionHelper::IsExtensionEnabled(
133 Profile
* profile
, const std::string
& name
) const {
134 return extensions::ExtensionSystem::Get(profile
)
135 ->extension_service()
136 ->IsExtensionEnabled(crx_file::id_util::GenerateId(name
));
139 void SyncExtensionHelper::IncognitoEnableExtension(
140 Profile
* profile
, const std::string
& name
) {
141 extensions::util::SetIsIncognitoEnabled(
142 crx_file::id_util::GenerateId(name
), profile
, true);
145 void SyncExtensionHelper::IncognitoDisableExtension(
146 Profile
* profile
, const std::string
& name
) {
147 extensions::util::SetIsIncognitoEnabled(
148 crx_file::id_util::GenerateId(name
), profile
, false);
151 bool SyncExtensionHelper::IsIncognitoEnabled(
152 Profile
* profile
, const std::string
& name
) const {
153 return extensions::util::IsIncognitoEnabled(
154 crx_file::id_util::GenerateId(name
), profile
);
158 bool SyncExtensionHelper::IsExtensionPendingInstallForSync(
159 Profile
* profile
, const std::string
& id
) const {
160 const extensions::PendingExtensionManager
* pending_extension_manager
=
161 extensions::ExtensionSystem::Get(profile
)
162 ->extension_service()
163 ->pending_extension_manager();
164 const extensions::PendingExtensionInfo
* info
=
165 pending_extension_manager
->GetById(id
);
168 return info
->is_from_sync();
171 void SyncExtensionHelper::InstallExtensionsPendingForSync(Profile
* profile
) {
172 // TODO(akalin): Mock out the servers that the extensions auto-update
173 // mechanism talk to so as to more closely match what actually happens.
174 // Background networking will need to be re-enabled for extensions tests.
176 // We make a copy here since InstallExtension() removes the
177 // extension from the extensions service's copy.
178 const extensions::PendingExtensionManager
* pending_extension_manager
=
179 extensions::ExtensionSystem::Get(profile
)
180 ->extension_service()
181 ->pending_extension_manager();
183 std::list
<std::string
> pending_crx_ids
;
184 pending_extension_manager
->GetPendingIdsForUpdateCheck(&pending_crx_ids
);
186 std::list
<std::string
>::const_iterator iter
;
187 const extensions::PendingExtensionInfo
* info
= NULL
;
188 for (iter
= pending_crx_ids
.begin(); iter
!= pending_crx_ids
.end(); ++iter
) {
189 ASSERT_TRUE((info
= pending_extension_manager
->GetById(*iter
)));
190 if (!info
->is_from_sync())
193 StringMap::const_iterator iter2
= id_to_name_
.find(*iter
);
194 if (iter2
== id_to_name_
.end()) {
195 ADD_FAILURE() << "Could not get name for id " << *iter
196 << " (profile = " << profile
->GetDebugName() << ")";
199 TypeMap::const_iterator iter3
= id_to_type_
.find(*iter
);
200 if (iter3
== id_to_type_
.end()) {
201 ADD_FAILURE() << "Could not get type for id " << *iter
202 << " (profile = " << profile
->GetDebugName() << ")";
204 InstallExtension(profile
, iter2
->second
, iter3
->second
);
208 SyncExtensionHelper::ExtensionStateMap
209 SyncExtensionHelper::GetExtensionStates(Profile
* profile
) {
210 const std::string
& profile_debug_name
= profile
->GetDebugName();
212 ExtensionStateMap extension_state_map
;
214 scoped_ptr
<const extensions::ExtensionSet
> extensions(
215 extensions::ExtensionRegistry::Get(profile
)
216 ->GenerateInstalledExtensionsSet());
218 ExtensionService
* extension_service
=
219 extensions::ExtensionSystem::Get(profile
)->extension_service();
220 for (const scoped_refptr
<const Extension
>& extension
: *extensions
) {
221 const std::string
& id
= extension
->id();
222 // When doing Chrome account sign in though the Gaia extension, the Gaia
223 // extensions gets installed once and used by multiple profiles. This will
224 // cause extension list of profiles to not match.
225 if (id
== extensions::kGaiaAuthExtensionId
)
227 ExtensionState
& extension_state
= extension_state_map
[id
];
228 extension_state
.enabled_state
=
229 extension_service
->IsExtensionEnabled(id
) ?
230 ExtensionState::ENABLED
:
231 ExtensionState::DISABLED
;
232 extension_state
.disable_reasons
=
233 ExtensionPrefs::Get(profile
)->GetDisableReasons(id
);
234 extension_state
.incognito_enabled
=
235 extensions::util::IsIncognitoEnabled(id
, profile
);
237 DVLOG(2) << "Extension " << id
<< " in profile " << profile_debug_name
238 << " is " << (extension_service
->IsExtensionEnabled(id
) ?
239 "enabled" : "disabled");
242 const extensions::PendingExtensionManager
* pending_extension_manager
=
243 extension_service
->pending_extension_manager();
245 std::list
<std::string
> pending_crx_ids
;
246 pending_extension_manager
->GetPendingIdsForUpdateCheck(&pending_crx_ids
);
248 for (const std::string
& id
: pending_crx_ids
) {
249 ExtensionState
& extension_state
= extension_state_map
[id
];
250 extension_state
.enabled_state
= ExtensionState::PENDING
;
251 extension_state
.disable_reasons
=
252 ExtensionPrefs::Get(profile
)->GetDisableReasons(id
);
253 extension_state
.incognito_enabled
=
254 extensions::util::IsIncognitoEnabled(id
, profile
);
255 DVLOG(2) << "Extension " << id
<< " in profile "
256 << profile_debug_name
<< " is pending";
259 return extension_state_map
;
262 bool SyncExtensionHelper::ExtensionStatesMatch(
263 Profile
* profile1
, Profile
* profile2
) {
264 const ExtensionStateMap
& state_map1
= GetExtensionStates(profile1
);
265 const ExtensionStateMap
& state_map2
= GetExtensionStates(profile2
);
266 if (state_map1
.size() != state_map2
.size()) {
267 DVLOG(1) << "Number of extensions for profile " << profile1
->GetDebugName()
268 << " does not match profile " << profile2
->GetDebugName();
272 ExtensionStateMap::const_iterator it1
= state_map1
.begin();
273 ExtensionStateMap::const_iterator it2
= state_map2
.begin();
274 while (it1
!= state_map1
.end()) {
275 if (it1
->first
!= it2
->first
) {
276 DVLOG(1) << "Extensions for profile " << profile1
->GetDebugName()
277 << " do not match profile " << profile2
->GetDebugName();
279 } else if (!sync_datatype_helper::test()->UsingExternalServers() &&
280 !it1
->second
.Equals(it2
->second
)) {
281 // If this test is run against real backend servers then we do not expect
282 // to install pending extensions. So, we don't check equality of
283 // ExtensionState of each extension per profile.
284 DVLOG(1) << "Extension states for profile " << profile1
->GetDebugName()
285 << " do not match profile " << profile2
->GetDebugName();
294 std::string
SyncExtensionHelper::CreateFakeExtensionName(int index
) {
295 return extension_name_prefix_
+ base::IntToString(index
);
298 bool SyncExtensionHelper::ExtensionNameToIndex(const std::string
& name
,
300 if (!(base::StartsWith(name
, extension_name_prefix_
,
301 base::CompareCase::SENSITIVE
) &&
302 base::StringToInt(name
.substr(extension_name_prefix_
.size()), index
))) {
303 LOG(WARNING
) << "Unable to convert extension name \"" << name
310 void SyncExtensionHelper::SetupProfile(Profile
* profile
) {
311 extensions::ExtensionSystem::Get(profile
)->InitForRegularProfile(true);
312 profile_extensions_
.insert(make_pair(profile
, ExtensionNameMap()));
317 std::string
NameToPublicKey(const std::string
& name
) {
318 std::string public_key
;
320 EXPECT_TRUE(Extension::ProducePEM(name
, &pem
) &&
321 Extension::FormatPEMForFileOutput(pem
, &public_key
,
322 true /* is_public */));
326 // TODO(akalin): Somehow unify this with MakeExtension() in
327 // extension_util_unittest.cc.
328 scoped_refptr
<Extension
> CreateExtension(const base::FilePath
& base_dir
,
329 const std::string
& name
,
330 Manifest::Type type
) {
331 base::DictionaryValue source
;
332 source
.SetString(extensions::manifest_keys::kName
, name
);
333 const std::string
& public_key
= NameToPublicKey(name
);
334 source
.SetString(extensions::manifest_keys::kPublicKey
, public_key
);
335 source
.SetString(extensions::manifest_keys::kVersion
, "0.0.0.0");
337 case Manifest::TYPE_EXTENSION
:
340 case Manifest::TYPE_THEME
:
341 source
.Set(extensions::manifest_keys::kTheme
,
342 new base::DictionaryValue());
344 case Manifest::TYPE_HOSTED_APP
:
345 case Manifest::TYPE_LEGACY_PACKAGED_APP
:
346 source
.Set(extensions::manifest_keys::kApp
, new base::DictionaryValue());
347 source
.SetString(extensions::manifest_keys::kLaunchWebURL
,
348 "http://www.example.com");
350 case Manifest::TYPE_PLATFORM_APP
: {
351 source
.Set(extensions::manifest_keys::kApp
, new base::DictionaryValue());
352 source
.Set(extensions::manifest_keys::kPlatformAppBackground
,
353 new base::DictionaryValue());
354 base::ListValue
* scripts
= new base::ListValue();
355 scripts
->AppendString("main.js");
356 source
.Set(extensions::manifest_keys::kPlatformAppBackgroundScripts
,
364 const base::FilePath sub_dir
= base::FilePath().AppendASCII(name
);
365 base::FilePath extension_dir
;
366 if (!base::PathExists(base_dir
) &&
367 !base::CreateDirectory(base_dir
)) {
371 if (!base::CreateTemporaryDirInDir(base_dir
, sub_dir
.value(),
377 scoped_refptr
<Extension
> extension
=
378 Extension::Create(extension_dir
, Manifest::INTERNAL
, source
,
379 Extension::NO_FLAGS
, &error
);
380 if (!error
.empty()) {
381 ADD_FAILURE() << error
;
384 if (!extension
.get()) {
388 if (extension
->name() != name
) {
389 EXPECT_EQ(name
, extension
->name());
392 if (extension
->GetType() != type
) {
393 EXPECT_EQ(type
, extension
->GetType());
401 scoped_refptr
<Extension
> SyncExtensionHelper::GetExtension(
402 Profile
* profile
, const std::string
& name
, Manifest::Type type
) {
407 ProfileExtensionNameMap::iterator it
= profile_extensions_
.find(profile
);
408 if (it
== profile_extensions_
.end()) {
412 ExtensionNameMap::const_iterator it2
= it
->second
.find(name
);
413 if (it2
!= it
->second
.end()) {
417 scoped_refptr
<Extension
> extension
=
418 CreateExtension(extensions::ExtensionSystem::Get(profile
)
419 ->extension_service()
420 ->install_directory(),
423 if (!extension
.get()) {
427 const std::string
& expected_id
= crx_file::id_util::GenerateId(name
);
428 if (extension
->id() != expected_id
) {
429 EXPECT_EQ(expected_id
, extension
->id());
432 DVLOG(2) << "created extension with name = "
433 << name
<< ", id = " << expected_id
;
434 (it
->second
)[name
] = extension
;
435 id_to_name_
[expected_id
] = name
;
436 id_to_type_
[expected_id
] = type
;