// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/extension_browsertest.h" #include <vector> #include "base/command_line.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/path_service.h" #include "base/string_number_conversions.h" #include "base/memory/scoped_temp_dir.h" #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/extension_creator.h" #include "chrome/browser/extensions/extension_error_reporter.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/omnibox/location_bar.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/ui_test_utils.h" #include "content/common/notification_registrar.h" #include "content/common/notification_service.h" #include "content/common/notification_type.h" ExtensionBrowserTest::ExtensionBrowserTest() : loaded_(false), installed_(false), extension_installs_observed_(0), target_page_action_count_(-1), target_visible_page_action_count_(-1) { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); } void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) { // This enables DOM automation for tab contentses. EnableDOMAutomation(); // This enables it for extension hosts. ExtensionHost::EnableDOMAutomation(); PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); test_data_dir_ = test_data_dir_.AppendASCII("extensions"); #if defined(OS_CHROMEOS) // This makes sure that we create the Default profile first, with no // ExtensionService and then the real profile with one, as we do when // running on chromeos. command_line->AppendSwitchASCII(switches::kLoginUser, "TestUser@gmail.com"); command_line->AppendSwitchASCII(switches::kLoginProfile, "user"); command_line->AppendSwitch(switches::kNoFirstRun); #endif } const Extension* ExtensionBrowserTest::LoadExtensionImpl( const FilePath& path, bool incognito_enabled, bool fileaccess_enabled) { ExtensionService* service = browser()->profile()->GetExtensionService(); { NotificationRegistrar registrar; registrar.Add(this, NotificationType::EXTENSION_LOADED, NotificationService::AllSources()); service->LoadExtension(path); ui_test_utils::RunMessageLoop(); } // Find the extension by iterating backwards since it is likely last. FilePath extension_path = path; file_util::AbsolutePath(&extension_path); const Extension* extension = NULL; for (ExtensionList::const_reverse_iterator iter = service->extensions()->rbegin(); iter != service->extensions()->rend(); ++iter) { if ((*iter)->path() == extension_path) { extension = *iter; break; } } if (!extension) return NULL; // The call to OnExtensionInstalled ensures the other extension prefs // are set up with the defaults. service->extension_prefs()->OnExtensionInstalled( extension, Extension::ENABLED, false); service->SetIsIncognitoEnabled(extension->id(), incognito_enabled); service->SetAllowFileAccess(extension, fileaccess_enabled); if (!WaitForExtensionHostsToLoad()) return NULL; return extension; } const Extension* ExtensionBrowserTest::LoadExtension(const FilePath& path) { return LoadExtensionImpl(path, false, true); } const Extension* ExtensionBrowserTest::LoadExtensionIncognito( const FilePath& path) { return LoadExtensionImpl(path, true, true); } const Extension* ExtensionBrowserTest::LoadExtensionNoFileAccess( const FilePath& path) { return LoadExtensionImpl(path, false, false); } const Extension* ExtensionBrowserTest::LoadExtensionIncognitoNoFileAccess( const FilePath& path) { return LoadExtensionImpl(path, true, false); } bool ExtensionBrowserTest::LoadExtensionAsComponent(const FilePath& path) { ExtensionService* service = browser()->profile()->GetExtensionService(); std::string manifest; if (!file_util::ReadFileToString(path.Append(Extension::kManifestFilename), &manifest)) return false; service->LoadComponentExtension( ExtensionService::ComponentExtensionInfo(manifest, path)); return true; } FilePath ExtensionBrowserTest::PackExtension(const FilePath& dir_path) { FilePath crx_path = temp_dir_.path().AppendASCII("temp.crx"); if (!file_util::Delete(crx_path, false)) { ADD_FAILURE() << "Failed to delete crx: " << crx_path.value(); return FilePath(); } FilePath pem_path = crx_path.DirName().AppendASCII("temp.pem"); if (!file_util::Delete(pem_path, false)) { ADD_FAILURE() << "Failed to delete pem: " << pem_path.value(); return FilePath(); } if (!file_util::PathExists(dir_path)) { ADD_FAILURE() << "Extension dir not found: " << dir_path.value(); return FilePath(); } scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); if (!creator->Run(dir_path, crx_path, FilePath(), // no existing pem, use empty path pem_path)) { ADD_FAILURE() << "ExtensionCreator::Run() failed."; return FilePath(); } if (!file_util::PathExists(crx_path)) { ADD_FAILURE() << crx_path.value() << " was not created."; return FilePath(); } return crx_path; } // This class is used to simulate an installation abort by the user. class MockAbortExtensionInstallUI : public ExtensionInstallUI { public: MockAbortExtensionInstallUI() : ExtensionInstallUI(NULL) {} // Simulate a user abort on an extension installation. virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) { delegate->InstallUIAbort(); MessageLoopForUI::current()->Quit(); } virtual void OnInstallSuccess(const Extension* extension, SkBitmap* icon) {} virtual void OnInstallFailure(const std::string& error) {} }; class MockAutoConfirmExtensionInstallUI : public ExtensionInstallUI { public: explicit MockAutoConfirmExtensionInstallUI(Profile* profile) : ExtensionInstallUI(profile) {} // Proceed without confirmation prompt. virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) { delegate->InstallUIProceed(); } }; bool ExtensionBrowserTest::InstallOrUpdateExtension(const std::string& id, const FilePath& path, InstallUIType ui_type, int expected_change) { return InstallOrUpdateExtension(id, path, ui_type, expected_change, browser()->profile()); } bool ExtensionBrowserTest::InstallOrUpdateExtension(const std::string& id, const FilePath& path, InstallUIType ui_type, int expected_change, Profile* profile) { ExtensionService* service = profile->GetExtensionService(); service->set_show_extensions_prompts(false); size_t num_before = service->extensions()->size(); { NotificationRegistrar registrar; registrar.Add(this, NotificationType::EXTENSION_LOADED, NotificationService::AllSources()); registrar.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED, NotificationService::AllSources()); registrar.Add(this, NotificationType::EXTENSION_INSTALL_ERROR, NotificationService::AllSources()); ExtensionInstallUI* install_ui = NULL; if (ui_type == INSTALL_UI_TYPE_CANCEL) install_ui = new MockAbortExtensionInstallUI(); else if (ui_type == INSTALL_UI_TYPE_NORMAL) install_ui = new ExtensionInstallUI(profile); else if (ui_type == INSTALL_UI_TYPE_AUTO_CONFIRM) install_ui = new MockAutoConfirmExtensionInstallUI(profile); // TODO(tessamac): Update callers to always pass an unpacked extension // and then always pack the extension here. FilePath crx_path = path; if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) { crx_path = PackExtension(path); } if (crx_path.empty()) return false; scoped_refptr<CrxInstaller> installer( new CrxInstaller(service, install_ui)); installer->set_expected_id(id); installer->InstallCrx(crx_path); ui_test_utils::RunMessageLoop(); } size_t num_after = service->extensions()->size(); if (num_after != (num_before + expected_change)) { VLOG(1) << "Num extensions before: " << base::IntToString(num_before) << " num after: " << base::IntToString(num_after) << " Installed extensions follow:"; for (size_t i = 0; i < service->extensions()->size(); ++i) VLOG(1) << " " << (*service->extensions())[i]->id(); VLOG(1) << "Errors follow:"; const std::vector<std::string>* errors = ExtensionErrorReporter::GetInstance()->GetErrors(); for (std::vector<std::string>::const_iterator iter = errors->begin(); iter != errors->end(); ++iter) VLOG(1) << *iter; return false; } return WaitForExtensionHostsToLoad(); } void ExtensionBrowserTest::ReloadExtension(const std::string& extension_id) { ExtensionService* service = browser()->profile()->GetExtensionService(); service->ReloadExtension(extension_id); ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_LOADED, NotificationService::AllSources()); } void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) { ExtensionService* service = browser()->profile()->GetExtensionService(); service->UnloadExtension(extension_id, UnloadedExtensionInfo::DISABLE); } void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) { ExtensionService* service = browser()->profile()->GetExtensionService(); service->UninstallExtension(extension_id, false, NULL); } void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) { ExtensionService* service = browser()->profile()->GetExtensionService(); service->DisableExtension(extension_id); } void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) { ExtensionService* service = browser()->profile()->GetExtensionService(); service->EnableExtension(extension_id); } bool ExtensionBrowserTest::WaitForPageActionCountChangeTo(int count) { LocationBarTesting* location_bar = browser()->window()->GetLocationBar()->GetLocationBarForTesting(); if (location_bar->PageActionCount() != count) { target_page_action_count_ = count; ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_PAGE_ACTION_COUNT_CHANGED, NotificationService::AllSources()); } return location_bar->PageActionCount() == count; } bool ExtensionBrowserTest::WaitForPageActionVisibilityChangeTo(int count) { LocationBarTesting* location_bar = browser()->window()->GetLocationBar()->GetLocationBarForTesting(); if (location_bar->PageActionVisibleCount() != count) { target_visible_page_action_count_ = count; ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED, NotificationService::AllSources()); } return location_bar->PageActionVisibleCount() == count; } bool ExtensionBrowserTest::WaitForExtensionHostsToLoad() { // Wait for all the extension hosts that exist to finish loading. NotificationRegistrar registrar; registrar.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING, NotificationService::AllSources()); ExtensionProcessManager* manager = browser()->profile()->GetExtensionProcessManager(); for (ExtensionProcessManager::const_iterator iter = manager->begin(); iter != manager->end();) { if ((*iter)->did_stop_loading()) { ++iter; } else { ui_test_utils::RunMessageLoop(); // Test activity may have modified the set of extension processes during // message processing, so re-start the iteration to catch added/removed // processes. iter = manager->begin(); } } return true; } bool ExtensionBrowserTest::WaitForExtensionInstall() { int before = extension_installs_observed_; ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_INSTALLED, NotificationService::AllSources()); return extension_installs_observed_ == (before + 1); } bool ExtensionBrowserTest::WaitForExtensionInstallError() { int before = extension_installs_observed_; ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_INSTALL_ERROR, NotificationService::AllSources()); return extension_installs_observed_ == before; } void ExtensionBrowserTest::WaitForExtensionLoad() { ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_LOADED, NotificationService::AllSources()); WaitForExtensionHostsToLoad(); } bool ExtensionBrowserTest::WaitForExtensionCrash( const std::string& extension_id) { ExtensionService* service = browser()->profile()->GetExtensionService(); if (!service->GetExtensionById(extension_id, true)) { // The extension is already unloaded, presumably due to a crash. return true; } ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_PROCESS_TERMINATED, NotificationService::AllSources()); return (service->GetExtensionById(extension_id, true) == NULL); } void ExtensionBrowserTest::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::EXTENSION_LOADED: last_loaded_extension_id_ = Details<const Extension>(details).ptr()->id(); VLOG(1) << "Got EXTENSION_LOADED notification."; MessageLoopForUI::current()->Quit(); break; case NotificationType::EXTENSION_UPDATE_DISABLED: VLOG(1) << "Got EXTENSION_UPDATE_DISABLED notification."; MessageLoopForUI::current()->Quit(); break; case NotificationType::EXTENSION_HOST_DID_STOP_LOADING: VLOG(1) << "Got EXTENSION_HOST_DID_STOP_LOADING notification."; MessageLoopForUI::current()->Quit(); break; case NotificationType::EXTENSION_INSTALLED: VLOG(1) << "Got EXTENSION_INSTALLED notification."; ++extension_installs_observed_; MessageLoopForUI::current()->Quit(); break; case NotificationType::EXTENSION_INSTALL_ERROR: VLOG(1) << "Got EXTENSION_INSTALL_ERROR notification."; MessageLoopForUI::current()->Quit(); break; case NotificationType::EXTENSION_PROCESS_CREATED: VLOG(1) << "Got EXTENSION_PROCESS_CREATED notification."; MessageLoopForUI::current()->Quit(); break; case NotificationType::EXTENSION_PROCESS_TERMINATED: VLOG(1) << "Got EXTENSION_PROCESS_TERMINATED notification."; MessageLoopForUI::current()->Quit(); break; case NotificationType::EXTENSION_PAGE_ACTION_COUNT_CHANGED: { LocationBarTesting* location_bar = browser()->window()->GetLocationBar()->GetLocationBarForTesting(); VLOG(1) << "Got EXTENSION_PAGE_ACTION_COUNT_CHANGED notification. Number " "of page actions: " << location_bar->PageActionCount(); if (location_bar->PageActionCount() == target_page_action_count_) { target_page_action_count_ = -1; MessageLoopForUI::current()->Quit(); } break; } case NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED: { LocationBarTesting* location_bar = browser()->window()->GetLocationBar()->GetLocationBarForTesting(); VLOG(1) << "Got EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED notification. " "Number of visible page actions: " << location_bar->PageActionVisibleCount(); if (location_bar->PageActionVisibleCount() == target_visible_page_action_count_) { target_visible_page_action_count_ = -1; MessageLoopForUI::current()->Quit(); } break; } default: NOTREACHED(); break; } }