// Copyright (c) 2012 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_util.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/browsertest_util.h"
#include "chrome/browser/extensions/component_loader.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_install_prompt.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/unpacked_installer.h"
#include "chrome/browser/extensions/updater/extension_cache_fake.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_set.h"
#include "sync/api/string_ordinal.h"
#if defined(OS_CHROMEOS)
#include "chromeos/chromeos_switches.h"
#endif
using extensions::Extension;
using extensions::ExtensionCreator;
using extensions::FeatureSwitch;
using extensions::Manifest;
ExtensionBrowserTest::ExtensionBrowserTest()
: loaded_(false),
installed_(false),
#if defined(OS_CHROMEOS)
set_chromeos_user_(true),
#endif
// Default channel is STABLE but override with UNKNOWN so that unlaunched
// or incomplete APIs can write tests.
current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN),
override_prompt_for_external_extensions_(
FeatureSwitch::prompt_for_external_extensions(),
false),
profile_(NULL) {
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
}
ExtensionBrowserTest::~ExtensionBrowserTest() {
}
Profile* ExtensionBrowserTest::profile() {
if (!profile_) {
if (browser())
profile_ = browser()->profile();
else
profile_ = ProfileManager::GetActiveUserProfile();
}
return profile_;
}
// static
const Extension* ExtensionBrowserTest::GetExtensionByPath(
const extensions::ExtensionSet* extensions, const base::FilePath& path) {
base::FilePath extension_path = base::MakeAbsoluteFilePath(path);
EXPECT_TRUE(!extension_path.empty());
for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
iter != extensions->end(); ++iter) {
if ((*iter)->path() == extension_path) {
return iter->get();
}
}
return NULL;
}
void ExtensionBrowserTest::SetUp() {
test_extension_cache_.reset(new extensions::ExtensionCacheFake());
InProcessBrowserTest::SetUp();
}
void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) {
PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
test_data_dir_ = test_data_dir_.AppendASCII("extensions");
observer_.reset(new ExtensionTestNotificationObserver(browser()));
#if defined(OS_CHROMEOS)
if (set_chromeos_user_) {
// 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(chromeos::switches::kLoginUser,
"TestUser@gmail.com");
command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "user");
}
#endif
}
void ExtensionBrowserTest::SetUpOnMainThread() {
InProcessBrowserTest::SetUpOnMainThread();
observer_.reset(new ExtensionTestNotificationObserver(browser()));
}
const Extension* ExtensionBrowserTest::LoadExtension(
const base::FilePath& path) {
return LoadExtensionWithFlags(path, kFlagEnableFileAccess);
}
const Extension* ExtensionBrowserTest::LoadExtensionIncognito(
const base::FilePath& path) {
return LoadExtensionWithFlags(path,
kFlagEnableFileAccess | kFlagEnableIncognito);
}
const Extension* ExtensionBrowserTest::LoadExtensionWithFlags(
const base::FilePath& path, int flags) {
return LoadExtensionWithInstallParam(path, flags, std::string());
}
const extensions::Extension*
ExtensionBrowserTest::LoadExtensionWithInstallParam(
const base::FilePath& path,
int flags,
const std::string& install_param) {
ExtensionService* service = extensions::ExtensionSystem::Get(
profile())->extension_service();
{
observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
content::NotificationService::AllSources());
scoped_refptr<extensions::UnpackedInstaller> installer(
extensions::UnpackedInstaller::Create(service));
installer->set_prompt_for_plugins(false);
installer->set_require_modern_manifest_version(
(flags & kFlagAllowOldManifestVersions) == 0);
installer->Load(path);
observer_->Wait();
}
// Find the loaded extension by its path. See crbug.com/59531 for why
// we cannot just use last_loaded_extension_id().
const Extension* extension = GetExtensionByPath(service->extensions(), path);
if (!extension)
return NULL;
if (!(flags & kFlagIgnoreManifestWarnings)) {
const std::vector<extensions::InstallWarning>& install_warnings =
extension->install_warnings();
if (!install_warnings.empty()) {
std::string install_warnings_message = base::StringPrintf(
"Unexpected warnings when loading test extension %s:\n",
path.AsUTF8Unsafe().c_str());
for (std::vector<extensions::InstallWarning>::const_iterator it =
install_warnings.begin(); it != install_warnings.end(); ++it) {
install_warnings_message += " " + it->message + "\n";
}
EXPECT_TRUE(extension->install_warnings().empty()) <<
install_warnings_message;
return NULL;
}
}
const std::string extension_id = extension->id();
if (!install_param.empty()) {
extensions::ExtensionPrefs::Get(profile())
->SetInstallParam(extension_id, install_param);
// Re-enable the extension if needed.
if (service->extensions()->Contains(extension_id)) {
content::WindowedNotificationObserver load_signal(
chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
content::Source<Profile>(profile()));
// Reload the extension so that the
// NOTIFICATION_EXTENSION_LOADED_DEPRECATED
// observers may access |install_param|.
service->ReloadExtension(extension_id);
load_signal.Wait();
extension = service->GetExtensionById(extension_id, false);
CHECK(extension) << extension_id << " not found after reloading.";
}
}
// Toggling incognito or file access will reload the extension, so wait for
// the reload and grab the new extension instance. The default state is
// incognito disabled and file access enabled, so we don't wait in those
// cases.
{
content::WindowedNotificationObserver load_signal(
chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
content::Source<Profile>(profile()));
CHECK(!extensions::util::IsIncognitoEnabled(extension_id, profile()));
if (flags & kFlagEnableIncognito) {
extensions::util::SetIsIncognitoEnabled(extension_id, profile(), true);
load_signal.Wait();
extension = service->GetExtensionById(extension_id, false);
CHECK(extension) << extension_id << " not found after reloading.";
}
}
{
content::WindowedNotificationObserver load_signal(
chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
content::Source<Profile>(profile()));
CHECK(extensions::util::AllowFileAccess(extension_id, profile()));
if (!(flags & kFlagEnableFileAccess)) {
extensions::util::SetAllowFileAccess(extension_id, profile(), false);
load_signal.Wait();
extension = service->GetExtensionById(extension_id, false);
CHECK(extension) << extension_id << " not found after reloading.";
}
}
if (!observer_->WaitForExtensionViewsToLoad())
return NULL;
return extension;
}
const Extension* ExtensionBrowserTest::LoadExtensionAsComponentWithManifest(
const base::FilePath& path,
const base::FilePath::CharType* manifest_relative_path) {
ExtensionService* service = extensions::ExtensionSystem::Get(
profile())->extension_service();
std::string manifest;
if (!base::ReadFileToString(path.Append(manifest_relative_path), &manifest)) {
return NULL;
}
std::string extension_id = service->component_loader()->Add(manifest, path);
const Extension* extension = service->extensions()->GetByID(extension_id);
if (!extension)
return NULL;
observer_->set_last_loaded_extension_id(extension->id());
return extension;
}
const Extension* ExtensionBrowserTest::LoadExtensionAsComponent(
const base::FilePath& path) {
return LoadExtensionAsComponentWithManifest(path,
extensions::kManifestFilename);
}
base::FilePath ExtensionBrowserTest::PackExtension(
const base::FilePath& dir_path) {
base::FilePath crx_path = temp_dir_.path().AppendASCII("temp.crx");
if (!base::DeleteFile(crx_path, false)) {
ADD_FAILURE() << "Failed to delete crx: " << crx_path.value();
return base::FilePath();
}
// Look for PEM files with the same name as the directory.
base::FilePath pem_path =
dir_path.ReplaceExtension(FILE_PATH_LITERAL(".pem"));
base::FilePath pem_path_out;
if (!base::PathExists(pem_path)) {
pem_path = base::FilePath();
pem_path_out = crx_path.DirName().AppendASCII("temp.pem");
if (!base::DeleteFile(pem_path_out, false)) {
ADD_FAILURE() << "Failed to delete pem: " << pem_path_out.value();
return base::FilePath();
}
}
return PackExtensionWithOptions(dir_path, crx_path, pem_path, pem_path_out);
}
base::FilePath ExtensionBrowserTest::PackExtensionWithOptions(
const base::FilePath& dir_path,
const base::FilePath& crx_path,
const base::FilePath& pem_path,
const base::FilePath& pem_out_path) {
if (!base::PathExists(dir_path)) {
ADD_FAILURE() << "Extension dir not found: " << dir_path.value();
return base::FilePath();
}
if (!base::PathExists(pem_path) && pem_out_path.empty()) {
ADD_FAILURE() << "Must specify a PEM file or PEM output path";
return base::FilePath();
}
scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
if (!creator->Run(dir_path,
crx_path,
pem_path,
pem_out_path,
ExtensionCreator::kOverwriteCRX)) {
ADD_FAILURE() << "ExtensionCreator::Run() failed: "
<< creator->error_message();
return base::FilePath();
}
if (!base::PathExists(crx_path)) {
ADD_FAILURE() << crx_path.value() << " was not created.";
return base::FilePath();
}
return crx_path;
}
// This class is used to simulate an installation abort by the user.
class MockAbortExtensionInstallPrompt : public ExtensionInstallPrompt {
public:
MockAbortExtensionInstallPrompt() : ExtensionInstallPrompt(NULL) {
}
// Simulate a user abort on an extension installation.
virtual void ConfirmInstall(
Delegate* delegate,
const Extension* extension,
const ShowDialogCallback& show_dialog_callback) OVERRIDE {
delegate->InstallUIAbort(true);
base::MessageLoopForUI::current()->Quit();
}
virtual void OnInstallSuccess(const Extension* extension,
SkBitmap* icon) OVERRIDE {}
virtual void OnInstallFailure(
const extensions::CrxInstallerError& error) OVERRIDE {}
};
class MockAutoConfirmExtensionInstallPrompt : public ExtensionInstallPrompt {
public:
explicit MockAutoConfirmExtensionInstallPrompt(
content::WebContents* web_contents)
: ExtensionInstallPrompt(web_contents) {}
// Proceed without confirmation prompt.
virtual void ConfirmInstall(
Delegate* delegate,
const Extension* extension,
const ShowDialogCallback& show_dialog_callback) OVERRIDE {
delegate->InstallUIProceed();
}
};
const Extension* ExtensionBrowserTest::UpdateExtensionWaitForIdle(
const std::string& id,
const base::FilePath& path,
int expected_change) {
return InstallOrUpdateExtension(id,
path,
INSTALL_UI_TYPE_NONE,
expected_change,
Manifest::INTERNAL,
browser(),
Extension::NO_FLAGS,
false,
false);
}
const Extension* ExtensionBrowserTest::InstallExtensionFromWebstore(
const base::FilePath& path,
int expected_change) {
return InstallOrUpdateExtension(std::string(),
path,
INSTALL_UI_TYPE_NONE,
expected_change,
Manifest::INTERNAL,
browser(),
Extension::FROM_WEBSTORE,
true,
false);
}
const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
const std::string& id,
const base::FilePath& path,
InstallUIType ui_type,
int expected_change) {
return InstallOrUpdateExtension(id,
path,
ui_type,
expected_change,
Manifest::INTERNAL,
browser(),
Extension::NO_FLAGS,
true,
false);
}
const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
const std::string& id,
const base::FilePath& path,
InstallUIType ui_type,
int expected_change,
Browser* browser,
Extension::InitFromValueFlags creation_flags) {
return InstallOrUpdateExtension(id,
path,
ui_type,
expected_change,
Manifest::INTERNAL,
browser,
creation_flags,
true,
false);
}
const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
const std::string& id,
const base::FilePath& path,
InstallUIType ui_type,
int expected_change,
Manifest::Location install_source) {
return InstallOrUpdateExtension(id,
path,
ui_type,
expected_change,
install_source,
browser(),
Extension::NO_FLAGS,
true,
false);
}
const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
const std::string& id,
const base::FilePath& path,
InstallUIType ui_type,
int expected_change,
Manifest::Location install_source,
Browser* browser,
Extension::InitFromValueFlags creation_flags,
bool install_immediately,
bool is_ephemeral) {
ExtensionService* service = profile()->GetExtensionService();
service->set_show_extensions_prompts(false);
size_t num_before = service->extensions()->size();
{
scoped_ptr<ExtensionInstallPrompt> install_ui;
if (ui_type == INSTALL_UI_TYPE_CANCEL) {
install_ui.reset(new MockAbortExtensionInstallPrompt());
} else if (ui_type == INSTALL_UI_TYPE_NORMAL) {
install_ui.reset(new ExtensionInstallPrompt(
browser->tab_strip_model()->GetActiveWebContents()));
} else if (ui_type == INSTALL_UI_TYPE_AUTO_CONFIRM) {
install_ui.reset(new MockAutoConfirmExtensionInstallPrompt(
browser->tab_strip_model()->GetActiveWebContents()));
}
// TODO(tessamac): Update callers to always pass an unpacked extension
// and then always pack the extension here.
base::FilePath crx_path = path;
if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) {
crx_path = PackExtension(path);
}
if (crx_path.empty())
return NULL;
scoped_refptr<extensions::CrxInstaller> installer(
extensions::CrxInstaller::Create(service, install_ui.Pass()));
installer->set_expected_id(id);
installer->set_creation_flags(creation_flags);
installer->set_install_source(install_source);
installer->set_install_immediately(install_immediately);
installer->set_is_ephemeral(is_ephemeral);
if (!installer->is_gallery_install()) {
installer->set_off_store_install_allow_reason(
extensions::CrxInstaller::OffStoreInstallAllowedInTest);
}
observer_->Watch(
chrome::NOTIFICATION_CRX_INSTALLER_DONE,
content::Source<extensions::CrxInstaller>(installer.get()));
installer->InstallCrx(crx_path);
observer_->Wait();
}
size_t num_after = service->extensions()->size();
EXPECT_EQ(num_before + expected_change, num_after);
if (num_before + expected_change != num_after) {
VLOG(1) << "Num extensions before: " << base::IntToString(num_before)
<< " num after: " << base::IntToString(num_after)
<< " Installed extensions follow:";
for (extensions::ExtensionSet::const_iterator it =
service->extensions()->begin();
it != service->extensions()->end(); ++it)
VLOG(1) << " " << (*it)->id();
VLOG(1) << "Errors follow:";
const std::vector<base::string16>* errors =
ExtensionErrorReporter::GetInstance()->GetErrors();
for (std::vector<base::string16>::const_iterator iter = errors->begin();
iter != errors->end(); ++iter)
VLOG(1) << *iter;
return NULL;
}
if (!observer_->WaitForExtensionViewsToLoad())
return NULL;
return service->GetExtensionById(last_loaded_extension_id(), false);
}
void ExtensionBrowserTest::ReloadExtension(const std::string extension_id) {
observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
content::NotificationService::AllSources());
ExtensionService* service =
extensions::ExtensionSystem::Get(profile())->extension_service();
service->ReloadExtension(extension_id);
observer_->Wait();
observer_->WaitForExtensionViewsToLoad();
}
void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) {
ExtensionService* service = extensions::ExtensionSystem::Get(
profile())->extension_service();
service->UnloadExtension(extension_id,
extensions::UnloadedExtensionInfo::REASON_DISABLE);
}
void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) {
ExtensionService* service = extensions::ExtensionSystem::Get(
profile())->extension_service();
service->UninstallExtension(extension_id, false, NULL);
}
void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) {
ExtensionService* service = extensions::ExtensionSystem::Get(
profile())->extension_service();
service->DisableExtension(extension_id, Extension::DISABLE_USER_ACTION);
}
void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) {
ExtensionService* service = extensions::ExtensionSystem::Get(
profile())->extension_service();
service->EnableExtension(extension_id);
}
void ExtensionBrowserTest::OpenWindow(content::WebContents* contents,
const GURL& url,
bool newtab_process_should_equal_opener,
content::WebContents** newtab_result) {
content::WindowedNotificationObserver windowed_observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
ASSERT_TRUE(content::ExecuteScript(contents,
"window.open('" + url.spec() + "');"));
// The above window.open call is not user-initiated, so it will create
// a popup window instead of a new tab in current window.
// The stop notification will come from the new tab.
windowed_observer.Wait();
content::NavigationController* controller =
content::Source<content::NavigationController>(
windowed_observer.source()).ptr();
content::WebContents* newtab = controller->GetWebContents();
ASSERT_TRUE(newtab);
EXPECT_EQ(url, controller->GetLastCommittedEntry()->GetURL());
if (newtab_process_should_equal_opener)
EXPECT_EQ(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost());
else
EXPECT_NE(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost());
if (newtab_result)
*newtab_result = newtab;
}
void ExtensionBrowserTest::NavigateInRenderer(content::WebContents* contents,
const GURL& url) {
bool result = false;
content::WindowedNotificationObserver windowed_observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
contents,
"window.addEventListener('unload', function() {"
" window.domAutomationController.send(true);"
"}, false);"
"window.location = '" + url.spec() + "';",
&result));
ASSERT_TRUE(result);
windowed_observer.Wait();
EXPECT_EQ(url, contents->GetController().GetLastCommittedEntry()->GetURL());
}
extensions::ExtensionHost* ExtensionBrowserTest::FindHostWithPath(
extensions::ProcessManager* manager,
const std::string& path,
int expected_hosts) {
extensions::ExtensionHost* host = NULL;
int num_hosts = 0;
extensions::ProcessManager::ExtensionHostSet background_hosts =
manager->background_hosts();
for (extensions::ProcessManager::const_iterator iter =
background_hosts.begin();
iter != background_hosts.end();
++iter) {
if ((*iter)->GetURL().path() == path) {
EXPECT_FALSE(host);
host = *iter;
}
num_hosts++;
}
EXPECT_EQ(expected_hosts, num_hosts);
return host;
}
std::string ExtensionBrowserTest::ExecuteScriptInBackgroundPage(
const std::string& extension_id,
const std::string& script) {
return extensions::browsertest_util::ExecuteScriptInBackgroundPage(
profile(), extension_id, script);
}
bool ExtensionBrowserTest::ExecuteScriptInBackgroundPageNoWait(
const std::string& extension_id,
const std::string& script) {
return extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
profile(), extension_id, script);
}