blob: 50dd4e49bbe2dd5b8538336e6dfb2f9a33cfa92a [file] [log] [blame]
// Copyright 2014 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/ui/webui/extensions/extension_loader_handler.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/extensions/unpacked_installer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/file_highlighter.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "grit/generated_resources.h"
#include "third_party/re2/re2/re2.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/select_file_dialog.h"
namespace extensions {
namespace {
// Read a file to a string and return.
std::string ReadFileToString(const base::FilePath& path) {
std::string data;
// This call can fail, but it doesn't matter for our purposes. If it fails,
// we simply return an empty string for the manifest, and ignore it.
base::ReadFileToString(path, &data);
return data;
}
} // namespace
class ExtensionLoaderHandler::FileHelper
: public ui::SelectFileDialog::Listener {
public:
explicit FileHelper(ExtensionLoaderHandler* loader_handler);
virtual ~FileHelper();
// Create a FileDialog for the user to select the unpacked extension
// directory.
void ChooseFile();
private:
// ui::SelectFileDialog::Listener implementation.
virtual void FileSelected(const base::FilePath& path,
int index,
void* params) OVERRIDE;
virtual void MultiFilesSelected(
const std::vector<base::FilePath>& files, void* params) OVERRIDE;
// The associated ExtensionLoaderHandler. Weak, but guaranteed to be alive,
// as it owns this object.
ExtensionLoaderHandler* loader_handler_;
// The dialog used to pick a directory when loading an unpacked extension.
scoped_refptr<ui::SelectFileDialog> load_extension_dialog_;
// The last selected directory, so we can start in the same spot.
base::FilePath last_unpacked_directory_;
// The title of the dialog.
base::string16 title_;
DISALLOW_COPY_AND_ASSIGN(FileHelper);
};
ExtensionLoaderHandler::FileHelper::FileHelper(
ExtensionLoaderHandler* loader_handler)
: loader_handler_(loader_handler),
title_(l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY)) {
}
ExtensionLoaderHandler::FileHelper::~FileHelper() {
// There may be a pending file dialog; inform it the listener is destroyed so
// it doesn't try and call back.
if (load_extension_dialog_.get())
load_extension_dialog_->ListenerDestroyed();
}
void ExtensionLoaderHandler::FileHelper::ChooseFile() {
static const int kFileTypeIndex = 0; // No file type information to index.
static const ui::SelectFileDialog::Type kSelectType =
ui::SelectFileDialog::SELECT_FOLDER;
if (!load_extension_dialog_.get()) {
load_extension_dialog_ = ui::SelectFileDialog::Create(
this,
new ChromeSelectFilePolicy(
loader_handler_->web_ui()->GetWebContents()));
}
load_extension_dialog_->SelectFile(
kSelectType,
title_,
last_unpacked_directory_,
NULL,
kFileTypeIndex,
base::FilePath::StringType(),
loader_handler_->web_ui()->
GetWebContents()->GetView()->GetTopLevelNativeWindow(),
NULL);
content::RecordComputedAction("Options_LoadUnpackedExtension");
}
void ExtensionLoaderHandler::FileHelper::FileSelected(
const base::FilePath& path, int index, void* params) {
loader_handler_->LoadUnpackedExtensionImpl(path);
}
void ExtensionLoaderHandler::FileHelper::MultiFilesSelected(
const std::vector<base::FilePath>& files, void* params) {
NOTREACHED();
}
ExtensionLoaderHandler::ExtensionLoaderHandler(Profile* profile)
: profile_(profile),
file_helper_(new FileHelper(this)),
weak_ptr_factory_(this) {
DCHECK(profile_);
}
ExtensionLoaderHandler::~ExtensionLoaderHandler() {
}
void ExtensionLoaderHandler::GetLocalizedValues(
content::WebUIDataSource* source) {
source->AddString(
"extensionLoadErrorHeading",
l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_HEADING));
source->AddString(
"extensionLoadErrorMessage",
l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_MESSAGE));
source->AddString(
"extensionLoadErrorRetry",
l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_RETRY));
source->AddString(
"extensionLoadErrorGiveUp",
l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_GIVE_UP));
source->AddString(
"extensionLoadCouldNotLoadManifest",
l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_COULD_NOT_LOAD_MANIFEST));
}
void ExtensionLoaderHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"extensionLoaderLoadUnpacked",
base::Bind(&ExtensionLoaderHandler::HandleLoadUnpacked,
weak_ptr_factory_.GetWeakPtr()));
web_ui()->RegisterMessageCallback(
"extensionLoaderRetry",
base::Bind(&ExtensionLoaderHandler::HandleRetry,
weak_ptr_factory_.GetWeakPtr()));
}
void ExtensionLoaderHandler::HandleLoadUnpacked(const base::ListValue* args) {
DCHECK(args->empty());
file_helper_->ChooseFile();
}
void ExtensionLoaderHandler::HandleRetry(const base::ListValue* args) {
DCHECK(args->empty());
LoadUnpackedExtensionImpl(failed_path_);
}
void ExtensionLoaderHandler::LoadUnpackedExtensionImpl(
const base::FilePath& file_path) {
scoped_refptr<UnpackedInstaller> installer = UnpackedInstaller::Create(
ExtensionSystem::Get(profile_)->extension_service());
installer->set_on_failure_callback(
base::Bind(&ExtensionLoaderHandler::OnLoadFailure,
weak_ptr_factory_.GetWeakPtr()));
installer->Load(file_path);
}
void ExtensionLoaderHandler::OnLoadFailure(const base::FilePath& file_path,
const std::string& error) {
failed_path_ = file_path;
size_t line = 0u;
size_t column = 0u;
std::string regex =
base::StringPrintf("%s Line: (\\d+), column: (\\d+), Syntax error.",
manifest_errors::kManifestParseError);
// If this was a JSON parse error, we can highlight the exact line with the
// error. Otherwise, we should still display the manifest (for consistency,
// reference, and so that if we ever make this really fancy and add an editor,
// it's ready).
//
// This regex call can fail, but if it does, we just don't highlight anything.
re2::RE2::FullMatch(error, regex, &line, &column);
// This will read the manifest and call NotifyFrontendOfFailure with the read
// manifest contents.
base::PostTaskAndReplyWithResult(
content::BrowserThread::GetBlockingPool(),
FROM_HERE,
base::Bind(&ReadFileToString, file_path.Append(kManifestFilename)),
base::Bind(&ExtensionLoaderHandler::NotifyFrontendOfFailure,
weak_ptr_factory_.GetWeakPtr(),
file_path,
error,
line));
}
void ExtensionLoaderHandler::NotifyFrontendOfFailure(
const base::FilePath& file_path,
const std::string& error,
size_t line_number,
const std::string& manifest) {
base::StringValue file_value(file_path.LossyDisplayName());
base::StringValue error_value(base::UTF8ToUTF16(error));
base::DictionaryValue manifest_value;
SourceHighlighter highlighter(manifest, line_number);
// If the line number is 0, this highlights no regions, but still adds the
// full manifest.
highlighter.SetHighlightedRegions(&manifest_value);
web_ui()->CallJavascriptFunction(
"extensions.ExtensionLoader.notifyLoadFailed",
file_value,
error_value,
manifest_value);
}
} // namespace extensions