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