OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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. |
| 4 |
| 5 #include "chrome/browser/extensions/extension_service_test_with_install.h" |
| 6 |
| 7 #include "base/files/file_util.h" |
| 8 #include "base/strings/utf_string_conversions.h" |
| 9 #include "chrome/browser/extensions/crx_installer.h" |
| 10 #include "chrome/browser/extensions/extension_creator.h" |
| 11 #include "chrome/browser/extensions/extension_error_reporter.h" |
| 12 #include "chrome/browser/profiles/profile.h" |
| 13 #include "content/public/browser/notification_service.h" |
| 14 #include "extensions/browser/extension_registry.h" |
| 15 #include "extensions/browser/notification_types.h" |
| 16 #include "testing/gtest/include/gtest/gtest.h" |
| 17 |
| 18 namespace extensions { |
| 19 |
| 20 namespace { |
| 21 |
| 22 struct ExtensionsOrder { |
| 23 bool operator()(const scoped_refptr<const Extension>& a, |
| 24 const scoped_refptr<const Extension>& b) { |
| 25 return a->name() < b->name(); |
| 26 } |
| 27 }; |
| 28 |
| 29 // Helper method to set up a WindowedNotificationObserver to wait for a |
| 30 // specific CrxInstaller to finish if we don't know the value of the |
| 31 // |installer| yet. |
| 32 bool IsCrxInstallerDone(extensions::CrxInstaller** installer, |
| 33 const content::NotificationSource& source, |
| 34 const content::NotificationDetails& details) { |
| 35 return content::Source<extensions::CrxInstaller>(source).ptr() == *installer; |
| 36 } |
| 37 |
| 38 } // namespace |
| 39 |
| 40 ExtensionServiceTestWithInstall::ExtensionServiceTestWithInstall() |
| 41 : installed_(nullptr), |
| 42 was_update_(false), |
| 43 unloaded_reason_(UnloadedExtensionInfo::REASON_UNDEFINED), |
| 44 expected_extensions_count_(0), |
| 45 override_external_install_prompt_( |
| 46 FeatureSwitch::prompt_for_external_extensions(), |
| 47 false) { |
| 48 // TODO(treib,devlin): This should use ExtensionRegistryObserver instead. |
| 49 registrar_.Add(this, |
| 50 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, |
| 51 content::NotificationService::AllSources()); |
| 52 registrar_.Add(this, |
| 53 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, |
| 54 content::NotificationService::AllSources()); |
| 55 registrar_.Add( |
| 56 this, |
| 57 extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED, |
| 58 content::NotificationService::AllSources()); |
| 59 } |
| 60 |
| 61 ExtensionServiceTestWithInstall::~ExtensionServiceTestWithInstall() {} |
| 62 |
| 63 // static |
| 64 std::vector<base::string16> ExtensionServiceTestWithInstall::GetErrors() { |
| 65 const std::vector<base::string16>* errors = |
| 66 ExtensionErrorReporter::GetInstance()->GetErrors(); |
| 67 std::vector<base::string16> ret_val; |
| 68 |
| 69 for (const base::string16& error : *errors) { |
| 70 std::string utf8_error = base::UTF16ToUTF8(error); |
| 71 if (utf8_error.find(".svn") == std::string::npos) { |
| 72 ret_val.push_back(error); |
| 73 } |
| 74 } |
| 75 |
| 76 // The tests rely on the errors being in a certain order, which can vary |
| 77 // depending on how filesystem iteration works. |
| 78 std::stable_sort(ret_val.begin(), ret_val.end()); |
| 79 |
| 80 return ret_val; |
| 81 } |
| 82 |
| 83 void ExtensionServiceTestWithInstall::PackCRX(const base::FilePath& dir_path, |
| 84 const base::FilePath& pem_path, |
| 85 const base::FilePath& crx_path) { |
| 86 // Use the existing pem key, if provided. |
| 87 base::FilePath pem_output_path; |
| 88 if (pem_path.value().empty()) { |
| 89 pem_output_path = crx_path.DirName().AppendASCII("temp.pem"); |
| 90 } else { |
| 91 ASSERT_TRUE(base::PathExists(pem_path)); |
| 92 } |
| 93 |
| 94 ASSERT_TRUE(base::DeleteFile(crx_path, false)); |
| 95 |
| 96 scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); |
| 97 ASSERT_TRUE(creator->Run(dir_path, |
| 98 crx_path, |
| 99 pem_path, |
| 100 pem_output_path, |
| 101 ExtensionCreator::kOverwriteCRX)); |
| 102 |
| 103 ASSERT_TRUE(base::PathExists(crx_path)); |
| 104 } |
| 105 |
| 106 const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( |
| 107 const base::FilePath& dir_path, |
| 108 const base::FilePath& pem_path, |
| 109 InstallState install_state, |
| 110 int creation_flags) { |
| 111 base::FilePath crx_path; |
| 112 base::ScopedTempDir temp_dir; |
| 113 EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); |
| 114 crx_path = temp_dir.path().AppendASCII("temp.crx"); |
| 115 |
| 116 PackCRX(dir_path, pem_path, crx_path); |
| 117 return InstallCRX(crx_path, install_state, creation_flags); |
| 118 } |
| 119 |
| 120 const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( |
| 121 const base::FilePath& dir_path, |
| 122 const base::FilePath& pem_path, |
| 123 InstallState install_state) { |
| 124 return PackAndInstallCRX(dir_path, pem_path, install_state, |
| 125 Extension::NO_FLAGS); |
| 126 } |
| 127 |
| 128 const Extension* ExtensionServiceTestWithInstall::PackAndInstallCRX( |
| 129 const base::FilePath& dir_path, |
| 130 InstallState install_state) { |
| 131 return PackAndInstallCRX(dir_path, base::FilePath(), install_state, |
| 132 Extension::NO_FLAGS); |
| 133 } |
| 134 |
| 135 // Attempts to install an extension. Use INSTALL_FAILED if the installation |
| 136 // is expected to fail. |
| 137 // If |install_state| is INSTALL_UPDATED, and |expected_old_name| is |
| 138 // non-empty, expects that the existing extension's title was |
| 139 // |expected_old_name|. |
| 140 const Extension* ExtensionServiceTestWithInstall::InstallCRX( |
| 141 const base::FilePath& path, |
| 142 InstallState install_state, |
| 143 int creation_flags, |
| 144 const std::string& expected_old_name) { |
| 145 InstallCRXInternal(path, creation_flags); |
| 146 return VerifyCrxInstall(path, install_state, expected_old_name); |
| 147 } |
| 148 |
| 149 // Attempts to install an extension. Use INSTALL_FAILED if the installation |
| 150 // is expected to fail. |
| 151 const Extension* ExtensionServiceTestWithInstall::InstallCRX( |
| 152 const base::FilePath& path, |
| 153 InstallState install_state, |
| 154 int creation_flags) { |
| 155 return InstallCRX(path, install_state, creation_flags, std::string()); |
| 156 } |
| 157 |
| 158 // Attempts to install an extension. Use INSTALL_FAILED if the installation |
| 159 // is expected to fail. |
| 160 const Extension* ExtensionServiceTestWithInstall::InstallCRX( |
| 161 const base::FilePath& path, |
| 162 InstallState install_state) { |
| 163 return InstallCRX(path, install_state, Extension::NO_FLAGS); |
| 164 } |
| 165 |
| 166 const Extension* ExtensionServiceTestWithInstall::InstallCRXFromWebStore( |
| 167 const base::FilePath& path, |
| 168 InstallState install_state) { |
| 169 InstallCRXInternal(path, Extension::FROM_WEBSTORE); |
| 170 return VerifyCrxInstall(path, install_state); |
| 171 } |
| 172 |
| 173 const Extension* ExtensionServiceTestWithInstall::InstallCRXWithLocation( |
| 174 const base::FilePath& crx_path, |
| 175 Manifest::Location install_location, |
| 176 InstallState install_state) { |
| 177 EXPECT_TRUE(base::PathExists(crx_path)) |
| 178 << "Path does not exist: "<< crx_path.value().c_str(); |
| 179 // no client (silent install) |
| 180 scoped_refptr<CrxInstaller> installer( |
| 181 CrxInstaller::CreateSilent(service())); |
| 182 installer->set_install_source(install_location); |
| 183 |
| 184 content::WindowedNotificationObserver observer( |
| 185 extensions::NOTIFICATION_CRX_INSTALLER_DONE, |
| 186 content::NotificationService::AllSources()); |
| 187 installer->InstallCrx(crx_path); |
| 188 observer.Wait(); |
| 189 |
| 190 return VerifyCrxInstall(crx_path, install_state); |
| 191 } |
| 192 |
| 193 const Extension* ExtensionServiceTestWithInstall::VerifyCrxInstall( |
| 194 const base::FilePath& path, |
| 195 InstallState install_state) { |
| 196 return VerifyCrxInstall(path, install_state, std::string()); |
| 197 } |
| 198 |
| 199 const Extension* ExtensionServiceTestWithInstall::VerifyCrxInstall( |
| 200 const base::FilePath& path, |
| 201 InstallState install_state, |
| 202 const std::string& expected_old_name) { |
| 203 std::vector<base::string16> errors = GetErrors(); |
| 204 const Extension* extension = NULL; |
| 205 if (install_state != INSTALL_FAILED) { |
| 206 if (install_state == INSTALL_NEW) |
| 207 ++expected_extensions_count_; |
| 208 |
| 209 EXPECT_TRUE(installed_) << path.value(); |
| 210 // If and only if INSTALL_UPDATED, it should have the is_update flag. |
| 211 EXPECT_EQ(install_state == INSTALL_UPDATED, was_update_) |
| 212 << path.value(); |
| 213 // If INSTALL_UPDATED, old_name_ should match the given string. |
| 214 if (install_state == INSTALL_UPDATED && !expected_old_name.empty()) |
| 215 EXPECT_EQ(expected_old_name, old_name_); |
| 216 EXPECT_EQ(0u, errors.size()) << path.value(); |
| 217 |
| 218 if (install_state == INSTALL_WITHOUT_LOAD) { |
| 219 EXPECT_EQ(0u, loaded_.size()) << path.value(); |
| 220 } else { |
| 221 EXPECT_EQ(1u, loaded_.size()) << path.value(); |
| 222 size_t actual_extension_count = |
| 223 registry()->enabled_extensions().size() + |
| 224 registry()->disabled_extensions().size(); |
| 225 EXPECT_EQ(expected_extensions_count_, actual_extension_count) << |
| 226 path.value(); |
| 227 extension = loaded_[0].get(); |
| 228 EXPECT_TRUE(service()->GetExtensionById(extension->id(), false)) |
| 229 << path.value(); |
| 230 } |
| 231 |
| 232 for (std::vector<base::string16>::iterator err = errors.begin(); |
| 233 err != errors.end(); ++err) { |
| 234 LOG(ERROR) << *err; |
| 235 } |
| 236 } else { |
| 237 EXPECT_FALSE(installed_) << path.value(); |
| 238 EXPECT_EQ(0u, loaded_.size()) << path.value(); |
| 239 EXPECT_EQ(1u, errors.size()) << path.value(); |
| 240 } |
| 241 |
| 242 installed_ = NULL; |
| 243 was_update_ = false; |
| 244 old_name_ = ""; |
| 245 loaded_.clear(); |
| 246 ExtensionErrorReporter::GetInstance()->ClearErrors(); |
| 247 return extension; |
| 248 } |
| 249 |
| 250 void ExtensionServiceTestWithInstall::PackCRXAndUpdateExtension( |
| 251 const std::string& id, |
| 252 const base::FilePath& dir_path, |
| 253 const base::FilePath& pem_path, |
| 254 UpdateState expected_state) { |
| 255 base::ScopedTempDir temp_dir; |
| 256 EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); |
| 257 base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx"); |
| 258 |
| 259 PackCRX(dir_path, pem_path, crx_path); |
| 260 UpdateExtension(id, crx_path, expected_state); |
| 261 } |
| 262 |
| 263 void ExtensionServiceTestWithInstall::UpdateExtension( |
| 264 const std::string& id, |
| 265 const base::FilePath& in_path, |
| 266 UpdateState expected_state) { |
| 267 ASSERT_TRUE(base::PathExists(in_path)); |
| 268 |
| 269 // We need to copy this to a temporary location because Update() will delete |
| 270 // it. |
| 271 base::FilePath path = temp_dir().path(); |
| 272 path = path.Append(in_path.BaseName()); |
| 273 ASSERT_TRUE(base::CopyFile(in_path, path)); |
| 274 |
| 275 int previous_enabled_extension_count = |
| 276 registry()->enabled_extensions().size(); |
| 277 int previous_installed_extension_count = |
| 278 previous_enabled_extension_count + |
| 279 registry()->disabled_extensions().size(); |
| 280 |
| 281 extensions::CrxInstaller* installer = NULL; |
| 282 content::WindowedNotificationObserver observer( |
| 283 extensions::NOTIFICATION_CRX_INSTALLER_DONE, |
| 284 base::Bind(&IsCrxInstallerDone, &installer)); |
| 285 service()->UpdateExtension(extensions::CRXFileInfo(id, path), true, |
| 286 &installer); |
| 287 |
| 288 if (installer) |
| 289 observer.Wait(); |
| 290 else |
| 291 base::RunLoop().RunUntilIdle(); |
| 292 |
| 293 std::vector<base::string16> errors = GetErrors(); |
| 294 int error_count = errors.size(); |
| 295 int enabled_extension_count = registry()->enabled_extensions().size(); |
| 296 int installed_extension_count = |
| 297 enabled_extension_count + registry()->disabled_extensions().size(); |
| 298 |
| 299 int expected_error_count = (expected_state == FAILED) ? 1 : 0; |
| 300 EXPECT_EQ(expected_error_count, error_count) << path.value(); |
| 301 |
| 302 if (expected_state <= FAILED) { |
| 303 EXPECT_EQ(previous_enabled_extension_count, |
| 304 enabled_extension_count); |
| 305 EXPECT_EQ(previous_installed_extension_count, |
| 306 installed_extension_count); |
| 307 } else { |
| 308 int expected_installed_extension_count = |
| 309 (expected_state >= INSTALLED) ? 1 : 0; |
| 310 int expected_enabled_extension_count = |
| 311 (expected_state >= ENABLED) ? 1 : 0; |
| 312 EXPECT_EQ(expected_installed_extension_count, |
| 313 installed_extension_count); |
| 314 EXPECT_EQ(expected_enabled_extension_count, |
| 315 enabled_extension_count); |
| 316 } |
| 317 |
| 318 // Update() should the temporary input file. |
| 319 EXPECT_FALSE(base::PathExists(path)); |
| 320 } |
| 321 |
| 322 void ExtensionServiceTestWithInstall::UninstallExtension(const std::string& id, |
| 323 bool use_helper) { |
| 324 UninstallExtension(id, use_helper, Extension::ENABLED); |
| 325 } |
| 326 |
| 327 void ExtensionServiceTestWithInstall::UninstallExtension( |
| 328 const std::string& id, |
| 329 bool use_helper, |
| 330 Extension::State expected_state) { |
| 331 // Verify that the extension is installed. |
| 332 base::FilePath extension_path = extensions_install_dir().AppendASCII(id); |
| 333 EXPECT_TRUE(base::PathExists(extension_path)); |
| 334 size_t pref_key_count = GetPrefKeyCount(); |
| 335 EXPECT_GT(pref_key_count, 0u); |
| 336 ValidateIntegerPref(id, "state", expected_state); |
| 337 |
| 338 // Uninstall it. |
| 339 if (use_helper) { |
| 340 EXPECT_TRUE(ExtensionService::UninstallExtensionHelper( |
| 341 service(), id, extensions::UNINSTALL_REASON_FOR_TESTING)); |
| 342 } else { |
| 343 EXPECT_TRUE(service()->UninstallExtension( |
| 344 id, |
| 345 extensions::UNINSTALL_REASON_FOR_TESTING, |
| 346 base::Bind(&base::DoNothing), |
| 347 NULL)); |
| 348 } |
| 349 --expected_extensions_count_; |
| 350 |
| 351 // We should get an unload notification. |
| 352 EXPECT_FALSE(unloaded_id_.empty()); |
| 353 EXPECT_EQ(id, unloaded_id_); |
| 354 |
| 355 // Verify uninstalled state. |
| 356 size_t new_pref_key_count = GetPrefKeyCount(); |
| 357 if (new_pref_key_count == pref_key_count) { |
| 358 ValidateIntegerPref(id, "state", |
| 359 Extension::EXTERNAL_EXTENSION_UNINSTALLED); |
| 360 } else { |
| 361 EXPECT_EQ(new_pref_key_count, pref_key_count - 1); |
| 362 } |
| 363 |
| 364 // The extension should not be in the service anymore. |
| 365 EXPECT_FALSE(service()->GetInstalledExtension(id)); |
| 366 base::RunLoop().RunUntilIdle(); |
| 367 |
| 368 // The directory should be gone. |
| 369 EXPECT_FALSE(base::PathExists(extension_path)); |
| 370 } |
| 371 |
| 372 void ExtensionServiceTestWithInstall::TerminateExtension( |
| 373 const std::string& id) { |
| 374 const Extension* extension = service()->GetInstalledExtension(id); |
| 375 if (!extension) { |
| 376 ADD_FAILURE(); |
| 377 return; |
| 378 } |
| 379 service()->TrackTerminatedExtensionForTest(extension); |
| 380 } |
| 381 |
| 382 void ExtensionServiceTestWithInstall::Observe( |
| 383 int type, |
| 384 const content::NotificationSource& source, |
| 385 const content::NotificationDetails& details) { |
| 386 switch (type) { |
| 387 case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: { |
| 388 const Extension* extension = |
| 389 content::Details<const Extension>(details).ptr(); |
| 390 loaded_.push_back(make_scoped_refptr(extension)); |
| 391 // The tests rely on the errors being in a certain order, which can vary |
| 392 // depending on how filesystem iteration works. |
| 393 std::stable_sort(loaded_.begin(), loaded_.end(), ExtensionsOrder()); |
| 394 break; |
| 395 } |
| 396 |
| 397 case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: { |
| 398 UnloadedExtensionInfo* unloaded_info = |
| 399 content::Details<UnloadedExtensionInfo>(details).ptr(); |
| 400 const Extension* e = unloaded_info->extension; |
| 401 unloaded_id_ = e->id(); |
| 402 unloaded_reason_ = unloaded_info->reason; |
| 403 extensions::ExtensionList::iterator i = |
| 404 std::find(loaded_.begin(), loaded_.end(), e); |
| 405 // TODO(erikkay) fix so this can be an assert. Right now the tests |
| 406 // are manually calling clear() on loaded_, so this isn't doable. |
| 407 if (i == loaded_.end()) |
| 408 return; |
| 409 loaded_.erase(i); |
| 410 break; |
| 411 } |
| 412 case extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED: { |
| 413 const extensions::InstalledExtensionInfo* installed_info = |
| 414 content::Details<const extensions::InstalledExtensionInfo>(details) |
| 415 .ptr(); |
| 416 installed_ = installed_info->extension; |
| 417 was_update_ = installed_info->is_update; |
| 418 old_name_ = installed_info->old_name; |
| 419 break; |
| 420 } |
| 421 |
| 422 default: |
| 423 DCHECK(false); |
| 424 } |
| 425 } |
| 426 |
| 427 // Create a CrxInstaller and install the CRX file. |
| 428 // Instead of calling this method yourself, use InstallCRX(), which does extra |
| 429 // error checking. |
| 430 void ExtensionServiceTestWithInstall::InstallCRXInternal( |
| 431 const base::FilePath& crx_path, |
| 432 int creation_flags) { |
| 433 ASSERT_TRUE(base::PathExists(crx_path)) |
| 434 << "Path does not exist: "<< crx_path.value().c_str(); |
| 435 scoped_refptr<CrxInstaller> installer( |
| 436 CrxInstaller::CreateSilent(service())); |
| 437 installer->set_creation_flags(creation_flags); |
| 438 if (!(creation_flags & Extension::WAS_INSTALLED_BY_DEFAULT)) |
| 439 installer->set_allow_silent_install(true); |
| 440 |
| 441 content::WindowedNotificationObserver observer( |
| 442 extensions::NOTIFICATION_CRX_INSTALLER_DONE, |
| 443 content::Source<extensions::CrxInstaller>(installer.get())); |
| 444 |
| 445 installer->InstallCrx(crx_path); |
| 446 |
| 447 observer.Wait(); |
| 448 } |
| 449 |
| 450 } // namespace extensions |
OLD | NEW |