From 97c4e8980c65df6fa56f87c09a820ec7626d2e02 Mon Sep 17 00:00:00 2001 From: "aa@chromium.org" Date: Mon, 31 Aug 2009 10:31:06 +0000 Subject: Adds a button to chrome://extensions/ that allows easy packing of extensions into crx files. BUG=20578 TEST=Create a sample extension, pack it, then try to install it. Review URL: http://codereview.chromium.org/181020 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24877 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/app/generated_resources.grd | 52 ++++ chrome/browser/browser_resources.grd | 2 +- chrome/browser/extensions/extension_creator.cc | 4 +- chrome/browser/extensions/extensions_ui.cc | 8 + chrome/browser/extensions/extensions_ui.h | 9 + chrome/browser/resources/extensions_ui.html | 20 +- .../views/extensions/extension_pack_dialog.cc | 291 +++++++++++++++++++++ chrome/chrome.gyp | 1 + 8 files changed, 380 insertions(+), 7 deletions(-) create mode 100644 chrome/browser/views/extensions/extension_pack_dialog.cc (limited to 'chrome') diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index dc6f840..cc3fc15 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2139,6 +2139,58 @@ each locale. aa1 --> Select the extension directory. + + Pack Extension + + + Select the root directory of the extension to pack. To update an extension, also select the private key file to reuse. + + + Extension root directory: + + + Private key file (optional): + + + Browse... + + + Select extension root directory. + + + Select private key file. + + + Private key + + + Extension root directory is required. + + + Extension root directory is invalid. + + + Private key file is invalid. + + + Extension Packaging Success + + + Created the following files: + +Extension: $1c:\users\aa\myextension.crx +Key File: $2c:\users\aa\myextension.pem + +Keep your key file in a safe place. You will need it to create new versions of your extension. + + + Created the extension: + +$1c:\users\aa\myextension.crx + + + Extension Packaging Failure + diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index d37bbaa..ca12d5d 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -1,6 +1,6 @@ +without changes to the corresponding grd file. aa3 --> diff --git a/chrome/browser/extensions/extension_creator.cc b/chrome/browser/extensions/extension_creator.cc index 7b7c389..00ea023 100644 --- a/chrome/browser/extensions/extension_creator.cc +++ b/chrome/browser/extensions/extension_creator.cc @@ -44,8 +44,8 @@ bool ExtensionCreator::InitializeInput( if (private_key_path.value().empty() && !private_key_output_path.value().empty() && file_util::PathExists(private_key_output_path)) { - error_message_ = "Private key exists next to input directory. Try using " - "--pack-extension-key"; + error_message_ = "A private key for specified extension already exists. " + "Reuse that key or delete it first."; return false; } diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc index d1e8781..e91610e 100644 --- a/chrome/browser/extensions/extensions_ui.cc +++ b/chrome/browser/extensions/extensions_ui.cc @@ -85,6 +85,8 @@ void ExtensionsDOMHandler::RegisterMessages() { NewCallback(this, &ExtensionsDOMHandler::HandleUninstallMessage)); dom_ui_->RegisterMessageCallback("load", NewCallback(this, &ExtensionsDOMHandler::HandleLoadMessage)); + dom_ui_->RegisterMessageCallback("pack", + NewCallback(this, &ExtensionsDOMHandler::HandlePackMessage)); } void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { @@ -183,6 +185,12 @@ void ExtensionsDOMHandler::HandleLoadMessage(const Value* value) { NULL, NULL); } +void ExtensionsDOMHandler::HandlePackMessage(const Value* value) { +#if defined(OS_WIN) + ShowPackDialog(); +#endif +} + void ExtensionsDOMHandler::FileSelected(const FilePath& path, int index, void* params) { extensions_service_->LoadExtension(path); diff --git a/chrome/browser/extensions/extensions_ui.h b/chrome/browser/extensions/extensions_ui.h index ad99ac5..c170dcb 100644 --- a/chrome/browser/extensions/extensions_ui.h +++ b/chrome/browser/extensions/extensions_ui.h @@ -72,6 +72,12 @@ class ExtensionsDOMHandler const FilePath& extension_path); private: +#if defined(OS_WIN) + // The implementation of this method is platform-specific and defined + // elsewhere. + static void ShowPackDialog(); +#endif + // Callback for "requestExtensionsData" message. void HandleRequestExtensionsData(const Value* value); @@ -90,6 +96,9 @@ class ExtensionsDOMHandler // Callback for "load" message. void HandleLoadMessage(const Value* value); + // Callback for "pack" message. + void HandlePackMessage(const Value* value); + // SelectFileDialog::Listener virtual void FileSelected(const FilePath& path, int index, void* params); diff --git a/chrome/browser/resources/extensions_ui.html b/chrome/browser/resources/extensions_ui.html index d0b8116..86abf84 100644 --- a/chrome/browser/resources/extensions_ui.html +++ b/chrome/browser/resources/extensions_ui.html @@ -57,7 +57,12 @@ div.content { font-size:84%; white-space:nowrap; margin-top:-5px; - margin-right:30px; + margin-right:2px; +} + +.sidebar-content button { + -webkit-box-sizing:border-box; + width:100%; } h2 { @@ -264,6 +269,13 @@ function handleUninstallExtension(node) { function loadExtension() { chrome.send('load', []); } + +/** + * Handles the "Pack extension..." button being pressed. + */ +function packExtension() { + chrome.send('pack', []); +} @@ -325,13 +337,13 @@ function loadExtension() { - - +

Tools

diff --git a/chrome/browser/views/extensions/extension_pack_dialog.cc b/chrome/browser/views/extensions/extension_pack_dialog.cc new file mode 100644 index 0000000..757ab0e --- /dev/null +++ b/chrome/browser/views/extensions/extension_pack_dialog.cc @@ -0,0 +1,291 @@ +// Copyright (c) 2009 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 "app/gfx/font.h" +#include "app/l10n_util.h" +#include "app/win_util.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_creator.h" +#include "chrome/browser/extensions/extensions_ui.h" +#include "chrome/browser/shell_dialogs.h" +#include "grit/generated_resources.h" +#include "views/controls/label.h" +#include "views/controls/button/native_button.h" +#include "views/controls/textfield/textfield.h" +#include "views/standard_layout.h" +#include "views/view.h" +#include "views/window/dialog_delegate.h" +#include "views/window/window.h" + +namespace { + +class PackDialogContent; + +// Manages packing an extension on the file thread and reporting the result +// back to the UI. +class PackExtensionJob : public base::RefCounted { + public: + PackExtensionJob(PackDialogContent* dialog_content, + const FilePath& root_directory, + const FilePath& key_file); + + // PackDialogContent calls this when it is closing, so that PackExtensionJob + // doesn't try to call back to the UI after it's destroyed. + void OnDialogClosed(); + + private: + void RunOnFileThread(); + void ReportSuccessOnUIThread(); + void ReportFailureOnUIThread(const std::string& error); + + MessageLoop* ui_loop_; + MessageLoop* file_loop_; + PackDialogContent* dialog_content_; + FilePath root_directory_; + FilePath key_file_; + FilePath crx_file_out_; + FilePath key_file_out_; + + DISALLOW_COPY_AND_ASSIGN(PackExtensionJob); +}; + + +// Puts up the the pack dialog, which has this basic layout: +// +// Select the extension to pack. +// +// Extension root: [ ] [browse] +// Extension key file: [ ] [browse] +// +// [ok] [cancel] +class PackDialogContent + : public views::View, + public SelectFileDialog::Listener, + public views::ButtonListener, + public views::DialogDelegate { + public: + PackDialogContent() { + using views::GridLayout; + + // Setup the layout. + views::GridLayout* layout = CreatePanelGridLayout(this); + SetLayoutManager(layout); + + views::ColumnSet* columns = layout->AddColumnSet(0); + columns->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + columns->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + columns->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, + GridLayout::USE_PREF, 0, 0); + columns->AddPaddingColumn(0, kRelatedControlHorizontalSpacing); + columns->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, + GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0, 0); + views::Label* heading = new views::Label( + l10n_util::GetString(IDS_EXTENSION_PACK_DIALOG_HEADING)); + heading->SetMultiLine(true); + heading->SetHorizontalAlignment(views::Label::ALIGN_LEFT); + layout->AddView(heading, 5, 1, GridLayout::FILL, GridLayout::LEADING); + + layout->AddPaddingRow(0, kUnrelatedControlVerticalSpacing); + + layout->StartRow(0, 0); + layout->AddView(new views::Label(l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_ROOT_DIRECTORY_LABEL))); + extension_root_textbox_ = new views::Textfield(); + extension_root_textbox_->set_default_width_in_chars(32); + layout->AddView(extension_root_textbox_); + extension_root_button_ = new views::NativeButton(this, l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_BROWSE)); + layout->AddView(extension_root_button_); + + layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); + + layout->StartRow(0, 0); + layout->AddView(new views::Label(l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_PRIVATE_KEY_LABEL))); + private_key_textbox_ = new views::Textfield(); + private_key_textbox_->set_default_width_in_chars(32); + layout->AddView(private_key_textbox_); + private_key_button_ = new views::NativeButton(this, l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_BROWSE)); + layout->AddView(private_key_button_); + } + + void OnPackSuccess(const FilePath& crx_file, const FilePath& pem_file) { + std::wstring message; + if (!pem_file.empty()) { + message = l10n_util::GetStringF( + IDS_EXTENSION_PACK_DIALOG_SUCCESS_BODY_NEW, + crx_file.ToWStringHack().c_str(), + pem_file.ToWStringHack().c_str()); + } else { + message = l10n_util::GetStringF( + IDS_EXTENSION_PACK_DIALOG_SUCCESS_BODY_UPDATE, + crx_file.ToWStringHack().c_str()); + } + win_util::MessageBox(GetWindow()->GetNativeWindow(), message, + l10n_util::GetString(IDS_EXTENSION_PACK_DIALOG_SUCCESS_TITLE), + MB_OK | MB_SETFOREGROUND); + GetWindow()->Close(); +} + + void OnPackFailure(const std::wstring& error) { + win_util::MessageBox(GetWindow()->GetNativeWindow(), error, + l10n_util::GetString(IDS_EXTENSION_PACK_DIALOG_FAILURE_TITLE), + MB_OK | MB_SETFOREGROUND); + } + + private: + // DialogDelegate + virtual bool Accept() { + FilePath root_directory = FilePath::FromWStringHack( + extension_root_textbox_->text()); + FilePath key_file = FilePath::FromWStringHack(private_key_textbox_->text()); + + if (root_directory.empty()) { + if (extension_root_textbox_->text().empty()) { + OnPackFailure(l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED)); + } else { + OnPackFailure(l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID)); + } + + return false; + } + + if (!private_key_textbox_->text().empty() && key_file.empty()) { + OnPackFailure(l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID)); + return false; + } + + pack_job_ = new PackExtensionJob(this, root_directory, key_file); + + // Prevent the dialog from closing because PackExtensionJob is asynchronous. + // We need to wait to find out if it succeeded before closing the window. + return false; + } + + virtual void OnClose() { + if (pack_job_) + pack_job_->OnDialogClosed(); + } + + // WindowDelegate + virtual std::wstring GetWindowTitle() const { + return l10n_util::GetString(IDS_EXTENSION_PACK_DIALOG_TITLE); + } + virtual views::View* GetContentsView() { + return this; + } + + // ButtonListener + virtual void ButtonPressed(views::Button* sender) { + file_dialog_ = SelectFileDialog::Create(this); + + if (sender == extension_root_button_) { + file_dialog_->SelectFile(SelectFileDialog::SELECT_FOLDER, + l10n_util::GetString(IDS_EXTENSION_PACK_DIALOG_SELECT_ROOT), + FilePath::FromWStringHack(extension_root_textbox_->text()), + NULL, 0, FILE_PATH_LITERAL(""), + GetWindow()->GetNativeWindow(), + extension_root_textbox_); + } else { + static SelectFileDialog::FileTypeInfo info; + info.extensions.push_back(std::vector()); + info.extensions.front().push_back(FILE_PATH_LITERAL("pem")); + info.extension_description_overrides.push_back(l10n_util::GetString( + IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION)); + info.include_all_files = true; + + file_dialog_->SelectFile(SelectFileDialog::SELECT_OPEN_FILE, + l10n_util::GetString(IDS_EXTENSION_PACK_DIALOG_SELECT_KEY), + FilePath::FromWStringHack(private_key_textbox_->text()), + &info, 1, FILE_PATH_LITERAL(""), + GetWindow()->GetNativeWindow(), + private_key_textbox_); + } + } + + // SelectFileDialog::Listener + virtual void MultiFilesSelected( + const std::vector& files, void* params) {}; + virtual void FileSelectionCanceled(void* params) {}; + virtual void FileSelected(const FilePath& path, int index, void* params) { + static_cast(params)->SetText(path.ToWStringHack()); + } + + views::Textfield* extension_root_textbox_; + views::Textfield* private_key_textbox_; + views::NativeButton* extension_root_button_; + views::NativeButton* private_key_button_; + + scoped_refptr file_dialog_; + scoped_refptr pack_job_; + + DISALLOW_COPY_AND_ASSIGN(PackDialogContent); +}; + + +PackExtensionJob::PackExtensionJob(PackDialogContent* dialog_content, + const FilePath& root_directory, + const FilePath& key_file) + : ui_loop_(MessageLoop::current()), dialog_content_(dialog_content), + root_directory_(root_directory), key_file_(key_file) { + ChromeThread::GetMessageLoop(ChromeThread::FILE)->PostTask(FROM_HERE, + NewRunnableMethod(this, &PackExtensionJob::RunOnFileThread)); +} + +void PackExtensionJob::OnDialogClosed() { + dialog_content_ = NULL; +} + +void PackExtensionJob::RunOnFileThread() { + crx_file_out_ = root_directory_.ReplaceExtension(FILE_PATH_LITERAL("crx")); + + if (key_file_.empty()) + key_file_out_ = root_directory_.ReplaceExtension(FILE_PATH_LITERAL("pem")); + + // TODO(aa): Need to internationalize the errors that ExtensionCreator + // returns. + ExtensionCreator creator; + if (creator.Run(root_directory_, crx_file_out_, key_file_, key_file_out_)) { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &PackExtensionJob::ReportSuccessOnUIThread)); + } else { + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &PackExtensionJob::ReportFailureOnUIThread, creator.error_message())); + } +} + +void PackExtensionJob::ReportSuccessOnUIThread() { + if (dialog_content_) + dialog_content_->OnPackSuccess(crx_file_out_, key_file_out_); +} + +void PackExtensionJob::ReportFailureOnUIThread(const std::string& error) { + if (dialog_content_) + dialog_content_->OnPackFailure(UTF8ToWide(error)); +} + +} // namespace + +// static +void ExtensionsDOMHandler::ShowPackDialog() { + Browser* browser = BrowserList::GetLastActive(); + if (!browser) + return; + + BrowserWindow* window = browser->window(); + if (!window) + return; + + views::Window::CreateChromeWindow(window->GetNativeHandle(), + gfx::Rect(400, 0), new PackDialogContent())->Show(); +} diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 63e2e89..81f13d7 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1893,6 +1893,7 @@ 'browser/views/event_utils.cc', 'browser/views/event_utils.h', 'browser/views/extensions/extension_install_prompt.cc', + 'browser/views/extensions/extension_pack_dialog.cc', 'browser/views/extensions/extension_shelf.cc', 'browser/views/extensions/extension_shelf.h', 'browser/views/extensions/extension_view.cc', -- cgit v1.1