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. 4 5 #include "chrome/browser/extensions/extension_browsertest.h" 6 7 #include <vector> 8 9 #include "base/command_line.h" 10 #include "base/file_util.h" 11 #include "base/files/file_path.h" 12 #include "base/files/scoped_temp_dir.h" 13 #include "base/path_service.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "chrome/browser/chrome_notification_types.h" 18 #include "chrome/browser/extensions/browsertest_util.h" 19 #include "chrome/browser/extensions/component_loader.h" 20 #include "chrome/browser/extensions/crx_installer.h" 21 #include "chrome/browser/extensions/extension_creator.h" 22 #include "chrome/browser/extensions/extension_error_reporter.h" 23 #include "chrome/browser/extensions/extension_host.h" 24 #include "chrome/browser/extensions/extension_install_prompt.h" 25 #include "chrome/browser/extensions/extension_service.h" 26 #include "chrome/browser/extensions/extension_system.h" 27 #include "chrome/browser/extensions/extension_util.h" 28 #include "chrome/browser/extensions/unpacked_installer.h" 29 #include "chrome/browser/profiles/profile.h" 30 #include "chrome/browser/profiles/profile_manager.h" 31 #include "chrome/browser/ui/browser.h" 32 #include "chrome/browser/ui/browser_window.h" 33 #include "chrome/browser/ui/tabs/tab_strip_model.h" 34 #include "chrome/common/chrome_paths.h" 35 #include "chrome/common/chrome_switches.h" 36 #include "chrome/common/chrome_version_info.h" 37 #include "chrome/common/extensions/extension_set.h" 38 #include "chrome/test/base/ui_test_utils.h" 39 #include "content/public/browser/navigation_controller.h" 40 #include "content/public/browser/navigation_entry.h" 41 #include "content/public/browser/notification_registrar.h" 42 #include "content/public/browser/notification_service.h" 43 #include "content/public/browser/render_view_host.h" 44 #include "content/public/test/browser_test_utils.h" 45 #include "extensions/common/constants.h" 46 #include "sync/api/string_ordinal.h" 47 48 #if defined(OS_CHROMEOS) 49 #include "chromeos/chromeos_switches.h" 50 #endif 51 52 using extensions::Extension; 53 using extensions::ExtensionCreator; 54 using extensions::FeatureSwitch; 55 using extensions::Manifest; 56 57 ExtensionBrowserTest::ExtensionBrowserTest() 58 : loaded_(false), 59 installed_(false), 60 current_channel_(chrome::VersionInfo::CHANNEL_DEV), 61 override_prompt_for_external_extensions_( 62 FeatureSwitch::prompt_for_external_extensions(), 63 false), 64 profile_(NULL) { 65 EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); 66 } 67 68 ExtensionBrowserTest::~ExtensionBrowserTest() { 69 } 70 71 Profile* ExtensionBrowserTest::profile() { 72 if (!profile_) { 73 if (browser()) 74 profile_ = browser()->profile(); 75 else 76 profile_ = ProfileManager::GetActiveUserProfile(); 77 } 78 return profile_; 79 } 80 81 // static 82 const Extension* ExtensionBrowserTest::GetExtensionByPath( 83 const ExtensionSet* extensions, const base::FilePath& path) { 84 base::FilePath extension_path = base::MakeAbsoluteFilePath(path); 85 EXPECT_TRUE(!extension_path.empty()); 86 for (ExtensionSet::const_iterator iter = extensions->begin(); 87 iter != extensions->end(); ++iter) { 88 if ((*iter)->path() == extension_path) { 89 return iter->get(); 90 } 91 } 92 return NULL; 93 } 94 95 void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) { 96 PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); 97 test_data_dir_ = test_data_dir_.AppendASCII("extensions"); 98 observer_.reset(new ExtensionTestNotificationObserver(browser())); 99 100 #if defined(OS_CHROMEOS) 101 // This makes sure that we create the Default profile first, with no 102 // ExtensionService and then the real profile with one, as we do when 103 // running on chromeos. 104 command_line->AppendSwitchASCII(chromeos::switches::kLoginUser, 105 "TestUser (at) gmail.com"); 106 command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "user"); 107 #endif 108 } 109 110 void ExtensionBrowserTest::SetUpOnMainThread() { 111 InProcessBrowserTest::SetUpOnMainThread(); 112 observer_.reset(new ExtensionTestNotificationObserver(browser())); 113 } 114 115 const Extension* ExtensionBrowserTest::LoadExtensionWithFlags( 116 const base::FilePath& path, int flags) { 117 ExtensionService* service = extensions::ExtensionSystem::Get( 118 profile())->extension_service(); 119 { 120 observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED, 121 content::NotificationService::AllSources()); 122 123 scoped_refptr<extensions::UnpackedInstaller> installer( 124 extensions::UnpackedInstaller::Create(service)); 125 installer->set_prompt_for_plugins(false); 126 installer->set_require_modern_manifest_version( 127 (flags & kFlagAllowOldManifestVersions) == 0); 128 installer->Load(path); 129 130 observer_->Wait(); 131 } 132 133 // Find the loaded extension by its path. See crbug.com/59531 for why 134 // we cannot just use last_loaded_extension_id(). 135 const Extension* extension = GetExtensionByPath(service->extensions(), path); 136 if (!extension) 137 return NULL; 138 139 if (!(flags & kFlagIgnoreManifestWarnings)) { 140 const std::vector<extensions::InstallWarning>& install_warnings = 141 extension->install_warnings(); 142 if (!install_warnings.empty()) { 143 std::string install_warnings_message = base::StringPrintf( 144 "Unexpected warnings when loading test extension %s:\n", 145 path.AsUTF8Unsafe().c_str()); 146 147 for (std::vector<extensions::InstallWarning>::const_iterator it = 148 install_warnings.begin(); it != install_warnings.end(); ++it) { 149 install_warnings_message += " " + it->message + "\n"; 150 } 151 152 EXPECT_TRUE(extension->install_warnings().empty()) << 153 install_warnings_message; 154 return NULL; 155 } 156 } 157 158 const std::string extension_id = extension->id(); 159 160 // The call to OnExtensionInstalled ensures the other extension prefs 161 // are set up with the defaults. 162 service->extension_prefs()->OnExtensionInstalled( 163 extension, 164 Extension::ENABLED, 165 false, 166 syncer::StringOrdinal::CreateInitialOrdinal()); 167 168 // Toggling incognito or file access will reload the extension, so wait for 169 // the reload and grab the new extension instance. The default state is 170 // incognito disabled and file access enabled, so we don't wait in those 171 // cases. 172 { 173 content::WindowedNotificationObserver load_signal( 174 chrome::NOTIFICATION_EXTENSION_LOADED, 175 content::Source<Profile>(profile())); 176 CHECK(!extension_util::IsIncognitoEnabled(extension_id, service) || 177 extension->force_incognito_enabled()); 178 179 if (flags & kFlagEnableIncognito) { 180 extension_util::SetIsIncognitoEnabled(extension_id, service, true); 181 load_signal.Wait(); 182 extension = service->GetExtensionById(extension_id, false); 183 CHECK(extension) << extension_id << " not found after reloading."; 184 } 185 } 186 187 { 188 content::WindowedNotificationObserver load_signal( 189 chrome::NOTIFICATION_EXTENSION_LOADED, 190 content::Source<Profile>(profile())); 191 CHECK(extension_util::AllowFileAccess(extension, service)); 192 if (!(flags & kFlagEnableFileAccess)) { 193 extension_util::SetAllowFileAccess(extension, service, false); 194 load_signal.Wait(); 195 extension = service->GetExtensionById(extension_id, false); 196 CHECK(extension) << extension_id << " not found after reloading."; 197 } 198 } 199 200 if (!observer_->WaitForExtensionViewsToLoad()) 201 return NULL; 202 203 return extension; 204 } 205 206 const Extension* ExtensionBrowserTest::LoadExtension( 207 const base::FilePath& path) { 208 return LoadExtensionWithFlags(path, kFlagEnableFileAccess); 209 } 210 211 const Extension* ExtensionBrowserTest::LoadExtensionIncognito( 212 const base::FilePath& path) { 213 return LoadExtensionWithFlags(path, 214 kFlagEnableFileAccess | kFlagEnableIncognito); 215 } 216 217 const Extension* ExtensionBrowserTest::LoadExtensionAsComponentWithManifest( 218 const base::FilePath& path, 219 const base::FilePath::CharType* manifest_relative_path) { 220 ExtensionService* service = extensions::ExtensionSystem::Get( 221 profile())->extension_service(); 222 223 std::string manifest; 224 if (!base::ReadFileToString(path.Append(manifest_relative_path), &manifest)) { 225 return NULL; 226 } 227 228 std::string extension_id = service->component_loader()->Add(manifest, path); 229 const Extension* extension = service->extensions()->GetByID(extension_id); 230 if (!extension) 231 return NULL; 232 observer_->set_last_loaded_extension_id(extension->id()); 233 return extension; 234 } 235 236 const Extension* ExtensionBrowserTest::LoadExtensionAsComponent( 237 const base::FilePath& path) { 238 return LoadExtensionAsComponentWithManifest(path, 239 extensions::kManifestFilename); 240 } 241 242 base::FilePath ExtensionBrowserTest::PackExtension( 243 const base::FilePath& dir_path) { 244 base::FilePath crx_path = temp_dir_.path().AppendASCII("temp.crx"); 245 if (!base::DeleteFile(crx_path, false)) { 246 ADD_FAILURE() << "Failed to delete crx: " << crx_path.value(); 247 return base::FilePath(); 248 } 249 250 // Look for PEM files with the same name as the directory. 251 base::FilePath pem_path = 252 dir_path.ReplaceExtension(FILE_PATH_LITERAL(".pem")); 253 base::FilePath pem_path_out; 254 255 if (!base::PathExists(pem_path)) { 256 pem_path = base::FilePath(); 257 pem_path_out = crx_path.DirName().AppendASCII("temp.pem"); 258 if (!base::DeleteFile(pem_path_out, false)) { 259 ADD_FAILURE() << "Failed to delete pem: " << pem_path_out.value(); 260 return base::FilePath(); 261 } 262 } 263 264 return PackExtensionWithOptions(dir_path, crx_path, pem_path, pem_path_out); 265 } 266 267 base::FilePath ExtensionBrowserTest::PackExtensionWithOptions( 268 const base::FilePath& dir_path, 269 const base::FilePath& crx_path, 270 const base::FilePath& pem_path, 271 const base::FilePath& pem_out_path) { 272 if (!base::PathExists(dir_path)) { 273 ADD_FAILURE() << "Extension dir not found: " << dir_path.value(); 274 return base::FilePath(); 275 } 276 277 if (!base::PathExists(pem_path) && pem_out_path.empty()) { 278 ADD_FAILURE() << "Must specify a PEM file or PEM output path"; 279 return base::FilePath(); 280 } 281 282 scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); 283 if (!creator->Run(dir_path, 284 crx_path, 285 pem_path, 286 pem_out_path, 287 ExtensionCreator::kOverwriteCRX)) { 288 ADD_FAILURE() << "ExtensionCreator::Run() failed: " 289 << creator->error_message(); 290 return base::FilePath(); 291 } 292 293 if (!base::PathExists(crx_path)) { 294 ADD_FAILURE() << crx_path.value() << " was not created."; 295 return base::FilePath(); 296 } 297 return crx_path; 298 } 299 300 // This class is used to simulate an installation abort by the user. 301 class MockAbortExtensionInstallPrompt : public ExtensionInstallPrompt { 302 public: 303 MockAbortExtensionInstallPrompt() : ExtensionInstallPrompt(NULL) { 304 } 305 306 // Simulate a user abort on an extension installation. 307 virtual void ConfirmInstall( 308 Delegate* delegate, 309 const Extension* extension, 310 const ShowDialogCallback& show_dialog_callback) OVERRIDE { 311 delegate->InstallUIAbort(true); 312 base::MessageLoopForUI::current()->Quit(); 313 } 314 315 virtual void OnInstallSuccess(const Extension* extension, 316 SkBitmap* icon) OVERRIDE {} 317 318 virtual void OnInstallFailure( 319 const extensions::CrxInstallerError& error) OVERRIDE {} 320 }; 321 322 class MockAutoConfirmExtensionInstallPrompt : public ExtensionInstallPrompt { 323 public: 324 explicit MockAutoConfirmExtensionInstallPrompt( 325 content::WebContents* web_contents) 326 : ExtensionInstallPrompt(web_contents) {} 327 328 // Proceed without confirmation prompt. 329 virtual void ConfirmInstall( 330 Delegate* delegate, 331 const Extension* extension, 332 const ShowDialogCallback& show_dialog_callback) OVERRIDE { 333 delegate->InstallUIProceed(); 334 } 335 }; 336 337 const Extension* ExtensionBrowserTest::UpdateExtensionWaitForIdle( 338 const std::string& id, 339 const base::FilePath& path, 340 int expected_change) { 341 return InstallOrUpdateExtension(id, 342 path, 343 INSTALL_UI_TYPE_NONE, 344 expected_change, 345 Manifest::INTERNAL, 346 browser(), 347 Extension::NO_FLAGS, 348 true); 349 } 350 351 const Extension* ExtensionBrowserTest::InstallExtensionFromWebstore( 352 const base::FilePath& path, 353 int expected_change) { 354 return InstallOrUpdateExtension(std::string(), 355 path, 356 INSTALL_UI_TYPE_NONE, 357 expected_change, 358 Manifest::INTERNAL, 359 browser(), 360 Extension::FROM_WEBSTORE, 361 false); 362 } 363 364 const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 365 const std::string& id, 366 const base::FilePath& path, 367 InstallUIType ui_type, 368 int expected_change) { 369 return InstallOrUpdateExtension(id, path, ui_type, expected_change, 370 Manifest::INTERNAL, browser(), Extension::NO_FLAGS, false); 371 } 372 373 const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 374 const std::string& id, 375 const base::FilePath& path, 376 InstallUIType ui_type, 377 int expected_change, 378 Browser* browser, 379 Extension::InitFromValueFlags creation_flags) { 380 return InstallOrUpdateExtension(id, path, ui_type, expected_change, 381 Manifest::INTERNAL, browser, creation_flags, 382 false); 383 } 384 385 const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 386 const std::string& id, 387 const base::FilePath& path, 388 InstallUIType ui_type, 389 int expected_change, 390 Manifest::Location install_source) { 391 return InstallOrUpdateExtension(id, path, ui_type, expected_change, 392 install_source, browser(), Extension::NO_FLAGS, false); 393 } 394 395 const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 396 const std::string& id, 397 const base::FilePath& path, 398 InstallUIType ui_type, 399 int expected_change, 400 Manifest::Location install_source, 401 Browser* browser, 402 Extension::InitFromValueFlags creation_flags, 403 bool wait_for_idle) { 404 ExtensionService* service = profile()->GetExtensionService(); 405 service->set_show_extensions_prompts(false); 406 size_t num_before = service->extensions()->size(); 407 408 { 409 scoped_ptr<ExtensionInstallPrompt> install_ui; 410 if (ui_type == INSTALL_UI_TYPE_CANCEL) { 411 install_ui.reset(new MockAbortExtensionInstallPrompt()); 412 } else if (ui_type == INSTALL_UI_TYPE_NORMAL) { 413 install_ui.reset(new ExtensionInstallPrompt( 414 browser->tab_strip_model()->GetActiveWebContents())); 415 } else if (ui_type == INSTALL_UI_TYPE_AUTO_CONFIRM) { 416 install_ui.reset(new MockAutoConfirmExtensionInstallPrompt( 417 browser->tab_strip_model()->GetActiveWebContents())); 418 } 419 420 // TODO(tessamac): Update callers to always pass an unpacked extension 421 // and then always pack the extension here. 422 base::FilePath crx_path = path; 423 if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) { 424 crx_path = PackExtension(path); 425 } 426 if (crx_path.empty()) 427 return NULL; 428 429 scoped_refptr<extensions::CrxInstaller> installer( 430 extensions::CrxInstaller::Create(service, install_ui.Pass())); 431 installer->set_expected_id(id); 432 installer->set_creation_flags(creation_flags); 433 installer->set_install_source(install_source); 434 installer->set_install_wait_for_idle(wait_for_idle); 435 if (!installer->is_gallery_install()) { 436 installer->set_off_store_install_allow_reason( 437 extensions::CrxInstaller::OffStoreInstallAllowedInTest); 438 } 439 440 observer_->Watch( 441 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 442 content::Source<extensions::CrxInstaller>(installer.get())); 443 444 installer->InstallCrx(crx_path); 445 446 observer_->Wait(); 447 } 448 449 size_t num_after = service->extensions()->size(); 450 EXPECT_EQ(num_before + expected_change, num_after); 451 if (num_before + expected_change != num_after) { 452 VLOG(1) << "Num extensions before: " << base::IntToString(num_before) 453 << " num after: " << base::IntToString(num_after) 454 << " Installed extensions follow:"; 455 456 for (ExtensionSet::const_iterator it = service->extensions()->begin(); 457 it != service->extensions()->end(); ++it) 458 VLOG(1) << " " << (*it)->id(); 459 460 VLOG(1) << "Errors follow:"; 461 const std::vector<base::string16>* errors = 462 ExtensionErrorReporter::GetInstance()->GetErrors(); 463 for (std::vector<base::string16>::const_iterator iter = errors->begin(); 464 iter != errors->end(); ++iter) 465 VLOG(1) << *iter; 466 467 return NULL; 468 } 469 470 if (!observer_->WaitForExtensionViewsToLoad()) 471 return NULL; 472 return service->GetExtensionById(last_loaded_extension_id(), false); 473 } 474 475 void ExtensionBrowserTest::ReloadExtension(const std::string extension_id) { 476 observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED, 477 content::NotificationService::AllSources()); 478 479 ExtensionService* service = 480 extensions::ExtensionSystem::Get(profile())->extension_service(); 481 service->ReloadExtension(extension_id); 482 483 observer_->Wait(); 484 observer_->WaitForExtensionViewsToLoad(); 485 } 486 487 void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) { 488 ExtensionService* service = extensions::ExtensionSystem::Get( 489 profile())->extension_service(); 490 service->UnloadExtension(extension_id, 491 extensions::UnloadedExtensionInfo::REASON_DISABLE); 492 } 493 494 void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) { 495 ExtensionService* service = extensions::ExtensionSystem::Get( 496 profile())->extension_service(); 497 service->UninstallExtension(extension_id, false, NULL); 498 } 499 500 void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) { 501 ExtensionService* service = extensions::ExtensionSystem::Get( 502 profile())->extension_service(); 503 service->DisableExtension(extension_id, Extension::DISABLE_USER_ACTION); 504 } 505 506 void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) { 507 ExtensionService* service = extensions::ExtensionSystem::Get( 508 profile())->extension_service(); 509 service->EnableExtension(extension_id); 510 } 511 512 void ExtensionBrowserTest::OpenWindow(content::WebContents* contents, 513 const GURL& url, 514 bool newtab_process_should_equal_opener, 515 content::WebContents** newtab_result) { 516 content::WindowedNotificationObserver windowed_observer( 517 content::NOTIFICATION_LOAD_STOP, 518 content::NotificationService::AllSources()); 519 ASSERT_TRUE(content::ExecuteScript(contents, 520 "window.open('" + url.spec() + "');")); 521 522 // The above window.open call is not user-initiated, so it will create 523 // a popup window instead of a new tab in current window. 524 // The stop notification will come from the new tab. 525 windowed_observer.Wait(); 526 content::NavigationController* controller = 527 content::Source<content::NavigationController>( 528 windowed_observer.source()).ptr(); 529 content::WebContents* newtab = controller->GetWebContents(); 530 ASSERT_TRUE(newtab); 531 EXPECT_EQ(url, controller->GetLastCommittedEntry()->GetURL()); 532 if (newtab_process_should_equal_opener) 533 EXPECT_EQ(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost()); 534 else 535 EXPECT_NE(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost()); 536 537 if (newtab_result) 538 *newtab_result = newtab; 539 } 540 541 void ExtensionBrowserTest::NavigateInRenderer(content::WebContents* contents, 542 const GURL& url) { 543 bool result = false; 544 content::WindowedNotificationObserver windowed_observer( 545 content::NOTIFICATION_LOAD_STOP, 546 content::NotificationService::AllSources()); 547 ASSERT_TRUE(content::ExecuteScriptAndExtractBool( 548 contents, 549 "window.addEventListener('unload', function() {" 550 " window.domAutomationController.send(true);" 551 "}, false);" 552 "window.location = '" + url.spec() + "';", 553 &result)); 554 ASSERT_TRUE(result); 555 windowed_observer.Wait(); 556 EXPECT_EQ(url, contents->GetController().GetLastCommittedEntry()->GetURL()); 557 } 558 559 extensions::ExtensionHost* ExtensionBrowserTest::FindHostWithPath( 560 extensions::ProcessManager* manager, 561 const std::string& path, 562 int expected_hosts) { 563 extensions::ExtensionHost* host = NULL; 564 int num_hosts = 0; 565 extensions::ProcessManager::ExtensionHostSet background_hosts = 566 manager->background_hosts(); 567 for (extensions::ProcessManager::const_iterator iter = 568 background_hosts.begin(); 569 iter != background_hosts.end(); 570 ++iter) { 571 if ((*iter)->GetURL().path() == path) { 572 EXPECT_FALSE(host); 573 host = *iter; 574 } 575 num_hosts++; 576 } 577 EXPECT_EQ(expected_hosts, num_hosts); 578 return host; 579 } 580 581 std::string ExtensionBrowserTest::ExecuteScriptInBackgroundPage( 582 const std::string& extension_id, 583 const std::string& script) { 584 return extensions::browsertest_util::ExecuteScriptInBackgroundPage( 585 profile(), extension_id, script); 586 } 587