extension_browsertest.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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::LoadExtension( 128 const base::FilePath& path) { 129 return LoadExtensionWithFlags(path, kFlagEnableFileAccess); 130} 131 132const Extension* ExtensionBrowserTest::LoadExtensionIncognito( 133 const base::FilePath& path) { 134 return LoadExtensionWithFlags(path, 135 kFlagEnableFileAccess | kFlagEnableIncognito); 136} 137 138const Extension* ExtensionBrowserTest::LoadExtensionWithFlags( 139 const base::FilePath& path, int flags) { 140 return LoadExtensionWithInstallParam(path, flags, std::string()); 141} 142 143const extensions::Extension* 144ExtensionBrowserTest::LoadExtensionWithInstallParam( 145 const base::FilePath& path, 146 int flags, 147 const std::string& install_param) { 148 ExtensionService* service = extensions::ExtensionSystem::Get( 149 profile())->extension_service(); 150 { 151 observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED, 152 content::NotificationService::AllSources()); 153 154 scoped_refptr<extensions::UnpackedInstaller> installer( 155 extensions::UnpackedInstaller::Create(service)); 156 installer->set_prompt_for_plugins(false); 157 installer->set_require_modern_manifest_version( 158 (flags & kFlagAllowOldManifestVersions) == 0); 159 installer->Load(path); 160 161 observer_->Wait(); 162 } 163 164 // Find the loaded extension by its path. See crbug.com/59531 for why 165 // we cannot just use last_loaded_extension_id(). 166 const Extension* extension = GetExtensionByPath(service->extensions(), path); 167 if (!extension) 168 return NULL; 169 170 if (!(flags & kFlagIgnoreManifestWarnings)) { 171 const std::vector<extensions::InstallWarning>& install_warnings = 172 extension->install_warnings(); 173 if (!install_warnings.empty()) { 174 std::string install_warnings_message = base::StringPrintf( 175 "Unexpected warnings when loading test extension %s:\n", 176 path.AsUTF8Unsafe().c_str()); 177 178 for (std::vector<extensions::InstallWarning>::const_iterator it = 179 install_warnings.begin(); it != install_warnings.end(); ++it) { 180 install_warnings_message += " " + it->message + "\n"; 181 } 182 183 EXPECT_TRUE(extension->install_warnings().empty()) << 184 install_warnings_message; 185 return NULL; 186 } 187 } 188 189 const std::string extension_id = extension->id(); 190 191 if (!install_param.empty()) { 192 extensions::ExtensionPrefs::Get(profile()) 193 ->SetInstallParam(extension_id, install_param); 194 // Re-enable the extension if needed. 195 if (service->extensions()->Contains(extension_id)) { 196 content::WindowedNotificationObserver load_signal( 197 chrome::NOTIFICATION_EXTENSION_LOADED, 198 content::Source<Profile>(profile())); 199 // Reload the extension so that the NOTIFICATION_EXTENSION_LOADED 200 // observers may access |install_param|. 201 service->ReloadExtension(extension_id); 202 load_signal.Wait(); 203 extension = service->GetExtensionById(extension_id, false); 204 CHECK(extension) << extension_id << " not found after reloading."; 205 } 206 } 207 208 // Toggling incognito or file access will reload the extension, so wait for 209 // the reload and grab the new extension instance. The default state is 210 // incognito disabled and file access enabled, so we don't wait in those 211 // cases. 212 { 213 content::WindowedNotificationObserver load_signal( 214 chrome::NOTIFICATION_EXTENSION_LOADED, 215 content::Source<Profile>(profile())); 216 CHECK(!extensions::util::IsIncognitoEnabled(extension_id, profile())); 217 218 if (flags & kFlagEnableIncognito) { 219 extensions::util::SetIsIncognitoEnabled(extension_id, profile(), true); 220 load_signal.Wait(); 221 extension = service->GetExtensionById(extension_id, false); 222 CHECK(extension) << extension_id << " not found after reloading."; 223 } 224 } 225 226 { 227 content::WindowedNotificationObserver load_signal( 228 chrome::NOTIFICATION_EXTENSION_LOADED, 229 content::Source<Profile>(profile())); 230 CHECK(extensions::util::AllowFileAccess(extension_id, profile())); 231 if (!(flags & kFlagEnableFileAccess)) { 232 extensions::util::SetAllowFileAccess(extension_id, profile(), false); 233 load_signal.Wait(); 234 extension = service->GetExtensionById(extension_id, false); 235 CHECK(extension) << extension_id << " not found after reloading."; 236 } 237 } 238 239 if (!observer_->WaitForExtensionViewsToLoad()) 240 return NULL; 241 242 return extension; 243} 244 245const Extension* ExtensionBrowserTest::LoadExtensionAsComponentWithManifest( 246 const base::FilePath& path, 247 const base::FilePath::CharType* manifest_relative_path) { 248 ExtensionService* service = extensions::ExtensionSystem::Get( 249 profile())->extension_service(); 250 251 std::string manifest; 252 if (!base::ReadFileToString(path.Append(manifest_relative_path), &manifest)) { 253 return NULL; 254 } 255 256 std::string extension_id = service->component_loader()->Add(manifest, path); 257 const Extension* extension = service->extensions()->GetByID(extension_id); 258 if (!extension) 259 return NULL; 260 observer_->set_last_loaded_extension_id(extension->id()); 261 return extension; 262} 263 264const Extension* ExtensionBrowserTest::LoadExtensionAsComponent( 265 const base::FilePath& path) { 266 return LoadExtensionAsComponentWithManifest(path, 267 extensions::kManifestFilename); 268} 269 270base::FilePath ExtensionBrowserTest::PackExtension( 271 const base::FilePath& dir_path) { 272 base::FilePath crx_path = temp_dir_.path().AppendASCII("temp.crx"); 273 if (!base::DeleteFile(crx_path, false)) { 274 ADD_FAILURE() << "Failed to delete crx: " << crx_path.value(); 275 return base::FilePath(); 276 } 277 278 // Look for PEM files with the same name as the directory. 279 base::FilePath pem_path = 280 dir_path.ReplaceExtension(FILE_PATH_LITERAL(".pem")); 281 base::FilePath pem_path_out; 282 283 if (!base::PathExists(pem_path)) { 284 pem_path = base::FilePath(); 285 pem_path_out = crx_path.DirName().AppendASCII("temp.pem"); 286 if (!base::DeleteFile(pem_path_out, false)) { 287 ADD_FAILURE() << "Failed to delete pem: " << pem_path_out.value(); 288 return base::FilePath(); 289 } 290 } 291 292 return PackExtensionWithOptions(dir_path, crx_path, pem_path, pem_path_out); 293} 294 295base::FilePath ExtensionBrowserTest::PackExtensionWithOptions( 296 const base::FilePath& dir_path, 297 const base::FilePath& crx_path, 298 const base::FilePath& pem_path, 299 const base::FilePath& pem_out_path) { 300 if (!base::PathExists(dir_path)) { 301 ADD_FAILURE() << "Extension dir not found: " << dir_path.value(); 302 return base::FilePath(); 303 } 304 305 if (!base::PathExists(pem_path) && pem_out_path.empty()) { 306 ADD_FAILURE() << "Must specify a PEM file or PEM output path"; 307 return base::FilePath(); 308 } 309 310 scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); 311 if (!creator->Run(dir_path, 312 crx_path, 313 pem_path, 314 pem_out_path, 315 ExtensionCreator::kOverwriteCRX)) { 316 ADD_FAILURE() << "ExtensionCreator::Run() failed: " 317 << creator->error_message(); 318 return base::FilePath(); 319 } 320 321 if (!base::PathExists(crx_path)) { 322 ADD_FAILURE() << crx_path.value() << " was not created."; 323 return base::FilePath(); 324 } 325 return crx_path; 326} 327 328// This class is used to simulate an installation abort by the user. 329class MockAbortExtensionInstallPrompt : public ExtensionInstallPrompt { 330 public: 331 MockAbortExtensionInstallPrompt() : ExtensionInstallPrompt(NULL) { 332 } 333 334 // Simulate a user abort on an extension installation. 335 virtual void ConfirmInstall( 336 Delegate* delegate, 337 const Extension* extension, 338 const ShowDialogCallback& show_dialog_callback) OVERRIDE { 339 delegate->InstallUIAbort(true); 340 base::MessageLoopForUI::current()->Quit(); 341 } 342 343 virtual void OnInstallSuccess(const Extension* extension, 344 SkBitmap* icon) OVERRIDE {} 345 346 virtual void OnInstallFailure( 347 const extensions::CrxInstallerError& error) OVERRIDE {} 348}; 349 350class MockAutoConfirmExtensionInstallPrompt : public ExtensionInstallPrompt { 351 public: 352 explicit MockAutoConfirmExtensionInstallPrompt( 353 content::WebContents* web_contents) 354 : ExtensionInstallPrompt(web_contents) {} 355 356 // Proceed without confirmation prompt. 357 virtual void ConfirmInstall( 358 Delegate* delegate, 359 const Extension* extension, 360 const ShowDialogCallback& show_dialog_callback) OVERRIDE { 361 delegate->InstallUIProceed(); 362 } 363}; 364 365const Extension* ExtensionBrowserTest::UpdateExtensionWaitForIdle( 366 const std::string& id, 367 const base::FilePath& path, 368 int expected_change) { 369 return InstallOrUpdateExtension(id, 370 path, 371 INSTALL_UI_TYPE_NONE, 372 expected_change, 373 Manifest::INTERNAL, 374 browser(), 375 Extension::NO_FLAGS, 376 true); 377} 378 379const Extension* ExtensionBrowserTest::InstallExtensionFromWebstore( 380 const base::FilePath& path, 381 int expected_change) { 382 return InstallOrUpdateExtension(std::string(), 383 path, 384 INSTALL_UI_TYPE_NONE, 385 expected_change, 386 Manifest::INTERNAL, 387 browser(), 388 Extension::FROM_WEBSTORE, 389 false); 390} 391 392const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 393 const std::string& id, 394 const base::FilePath& path, 395 InstallUIType ui_type, 396 int expected_change) { 397 return InstallOrUpdateExtension(id, path, ui_type, expected_change, 398 Manifest::INTERNAL, browser(), Extension::NO_FLAGS, false); 399} 400 401const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 402 const std::string& id, 403 const base::FilePath& path, 404 InstallUIType ui_type, 405 int expected_change, 406 Browser* browser, 407 Extension::InitFromValueFlags creation_flags) { 408 return InstallOrUpdateExtension(id, path, ui_type, expected_change, 409 Manifest::INTERNAL, browser, creation_flags, 410 false); 411} 412 413const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 414 const std::string& id, 415 const base::FilePath& path, 416 InstallUIType ui_type, 417 int expected_change, 418 Manifest::Location install_source) { 419 return InstallOrUpdateExtension(id, path, ui_type, expected_change, 420 install_source, browser(), Extension::NO_FLAGS, false); 421} 422 423const Extension* ExtensionBrowserTest::InstallOrUpdateExtension( 424 const std::string& id, 425 const base::FilePath& path, 426 InstallUIType ui_type, 427 int expected_change, 428 Manifest::Location install_source, 429 Browser* browser, 430 Extension::InitFromValueFlags creation_flags, 431 bool wait_for_idle) { 432 ExtensionService* service = profile()->GetExtensionService(); 433 service->set_show_extensions_prompts(false); 434 size_t num_before = service->extensions()->size(); 435 436 { 437 scoped_ptr<ExtensionInstallPrompt> install_ui; 438 if (ui_type == INSTALL_UI_TYPE_CANCEL) { 439 install_ui.reset(new MockAbortExtensionInstallPrompt()); 440 } else if (ui_type == INSTALL_UI_TYPE_NORMAL) { 441 install_ui.reset(new ExtensionInstallPrompt( 442 browser->tab_strip_model()->GetActiveWebContents())); 443 } else if (ui_type == INSTALL_UI_TYPE_AUTO_CONFIRM) { 444 install_ui.reset(new MockAutoConfirmExtensionInstallPrompt( 445 browser->tab_strip_model()->GetActiveWebContents())); 446 } 447 448 // TODO(tessamac): Update callers to always pass an unpacked extension 449 // and then always pack the extension here. 450 base::FilePath crx_path = path; 451 if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) { 452 crx_path = PackExtension(path); 453 } 454 if (crx_path.empty()) 455 return NULL; 456 457 scoped_refptr<extensions::CrxInstaller> installer( 458 extensions::CrxInstaller::Create(service, install_ui.Pass())); 459 installer->set_expected_id(id); 460 installer->set_creation_flags(creation_flags); 461 installer->set_install_source(install_source); 462 installer->set_install_wait_for_idle(wait_for_idle); 463 if (!installer->is_gallery_install()) { 464 installer->set_off_store_install_allow_reason( 465 extensions::CrxInstaller::OffStoreInstallAllowedInTest); 466 } 467 468 observer_->Watch( 469 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 470 content::Source<extensions::CrxInstaller>(installer.get())); 471 472 installer->InstallCrx(crx_path); 473 474 observer_->Wait(); 475 } 476 477 size_t num_after = service->extensions()->size(); 478 EXPECT_EQ(num_before + expected_change, num_after); 479 if (num_before + expected_change != num_after) { 480 VLOG(1) << "Num extensions before: " << base::IntToString(num_before) 481 << " num after: " << base::IntToString(num_after) 482 << " Installed extensions follow:"; 483 484 for (extensions::ExtensionSet::const_iterator it = 485 service->extensions()->begin(); 486 it != service->extensions()->end(); ++it) 487 VLOG(1) << " " << (*it)->id(); 488 489 VLOG(1) << "Errors follow:"; 490 const std::vector<base::string16>* errors = 491 ExtensionErrorReporter::GetInstance()->GetErrors(); 492 for (std::vector<base::string16>::const_iterator iter = errors->begin(); 493 iter != errors->end(); ++iter) 494 VLOG(1) << *iter; 495 496 return NULL; 497 } 498 499 if (!observer_->WaitForExtensionViewsToLoad()) 500 return NULL; 501 return service->GetExtensionById(last_loaded_extension_id(), false); 502} 503 504void ExtensionBrowserTest::ReloadExtension(const std::string extension_id) { 505 observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED, 506 content::NotificationService::AllSources()); 507 508 ExtensionService* service = 509 extensions::ExtensionSystem::Get(profile())->extension_service(); 510 service->ReloadExtension(extension_id); 511 512 observer_->Wait(); 513 observer_->WaitForExtensionViewsToLoad(); 514} 515 516void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) { 517 ExtensionService* service = extensions::ExtensionSystem::Get( 518 profile())->extension_service(); 519 service->UnloadExtension(extension_id, 520 extensions::UnloadedExtensionInfo::REASON_DISABLE); 521} 522 523void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) { 524 ExtensionService* service = extensions::ExtensionSystem::Get( 525 profile())->extension_service(); 526 service->UninstallExtension(extension_id, false, NULL); 527} 528 529void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) { 530 ExtensionService* service = extensions::ExtensionSystem::Get( 531 profile())->extension_service(); 532 service->DisableExtension(extension_id, Extension::DISABLE_USER_ACTION); 533} 534 535void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) { 536 ExtensionService* service = extensions::ExtensionSystem::Get( 537 profile())->extension_service(); 538 service->EnableExtension(extension_id); 539} 540 541void ExtensionBrowserTest::OpenWindow(content::WebContents* contents, 542 const GURL& url, 543 bool newtab_process_should_equal_opener, 544 content::WebContents** newtab_result) { 545 content::WindowedNotificationObserver windowed_observer( 546 content::NOTIFICATION_LOAD_STOP, 547 content::NotificationService::AllSources()); 548 ASSERT_TRUE(content::ExecuteScript(contents, 549 "window.open('" + url.spec() + "');")); 550 551 // The above window.open call is not user-initiated, so it will create 552 // a popup window instead of a new tab in current window. 553 // The stop notification will come from the new tab. 554 windowed_observer.Wait(); 555 content::NavigationController* controller = 556 content::Source<content::NavigationController>( 557 windowed_observer.source()).ptr(); 558 content::WebContents* newtab = controller->GetWebContents(); 559 ASSERT_TRUE(newtab); 560 EXPECT_EQ(url, controller->GetLastCommittedEntry()->GetURL()); 561 if (newtab_process_should_equal_opener) 562 EXPECT_EQ(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost()); 563 else 564 EXPECT_NE(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost()); 565 566 if (newtab_result) 567 *newtab_result = newtab; 568} 569 570void ExtensionBrowserTest::NavigateInRenderer(content::WebContents* contents, 571 const GURL& url) { 572 bool result = false; 573 content::WindowedNotificationObserver windowed_observer( 574 content::NOTIFICATION_LOAD_STOP, 575 content::NotificationService::AllSources()); 576 ASSERT_TRUE(content::ExecuteScriptAndExtractBool( 577 contents, 578 "window.addEventListener('unload', function() {" 579 " window.domAutomationController.send(true);" 580 "}, false);" 581 "window.location = '" + url.spec() + "';", 582 &result)); 583 ASSERT_TRUE(result); 584 windowed_observer.Wait(); 585 EXPECT_EQ(url, contents->GetController().GetLastCommittedEntry()->GetURL()); 586} 587 588extensions::ExtensionHost* ExtensionBrowserTest::FindHostWithPath( 589 extensions::ProcessManager* manager, 590 const std::string& path, 591 int expected_hosts) { 592 extensions::ExtensionHost* host = NULL; 593 int num_hosts = 0; 594 extensions::ProcessManager::ExtensionHostSet background_hosts = 595 manager->background_hosts(); 596 for (extensions::ProcessManager::const_iterator iter = 597 background_hosts.begin(); 598 iter != background_hosts.end(); 599 ++iter) { 600 if ((*iter)->GetURL().path() == path) { 601 EXPECT_FALSE(host); 602 host = *iter; 603 } 604 num_hosts++; 605 } 606 EXPECT_EQ(expected_hosts, num_hosts); 607 return host; 608} 609 610std::string ExtensionBrowserTest::ExecuteScriptInBackgroundPage( 611 const std::string& extension_id, 612 const std::string& script) { 613 return extensions::browsertest_util::ExecuteScriptInBackgroundPage( 614 profile(), extension_id, script); 615} 616