diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-31 10:31:06 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-31 10:31:06 +0000 |
commit | 97c4e8980c65df6fa56f87c09a820ec7626d2e02 (patch) | |
tree | a370d79cd5a6b187480d6ba1c049105ffc228796 /chrome | |
parent | 4eef339d670cfc2c884719347558647aab609f76 (diff) | |
download | chromium_src-97c4e8980c65df6fa56f87c09a820ec7626d2e02.zip chromium_src-97c4e8980c65df6fa56f87c09a820ec7626d2e02.tar.gz chromium_src-97c4e8980c65df6fa56f87c09a820ec7626d2e02.tar.bz2 |
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
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/app/generated_resources.grd | 52 | ||||
-rw-r--r-- | chrome/browser/browser_resources.grd | 2 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_creator.cc | 4 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_ui.cc | 8 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_ui.h | 9 | ||||
-rw-r--r-- | chrome/browser/resources/extensions_ui.html | 20 | ||||
-rw-r--r-- | chrome/browser/views/extensions/extension_pack_dialog.cc | 291 | ||||
-rw-r--r-- | chrome/chrome.gyp | 1 |
8 files changed, 380 insertions, 7 deletions
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 --> <message name="IDS_EXTENSION_LOAD_FROM_DIRECTORY" desc="Title of directory browse dialog when user wants to load an extension from a directory."> Select the extension directory. </message> + <message name="IDS_EXTENSION_PACK_DIALOG_TITLE" desc="Title of pack extension dialog"> + Pack Extension + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_HEADING" desc="The heading of the pack extension dialog."> + Select the root directory of the extension to pack. To update an extension, also select the private key file to reuse. + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_ROOT_DIRECTORY_LABEL" desc="Label in the pack extension dialog for the control that lets the user select the extension to pack."> + Extension root directory: + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_PRIVATE_KEY_LABEL" desc="Label in the pack extension dialog for the control that lets the user select the private key to use to pack the extension. The label should indicate that the input is not required to be filled in."> + Private key file (optional): + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_BROWSE" desc="Text on buttons in the pack extension dialog that open up file browsing UI to pick the extension to pack."> + Browse... + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_SELECT_ROOT" desc="Heading in file browse dialog instructing user to select an extension to pack."> + Select extension root directory. + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_SELECT_KEY" desc="Heading in file browse dialog instructing user to select a private key."> + Select private key file. + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION" desc="Displayed in the file browse dialog. Associates the file extension 'pem' is for Private Keys."> + Private key + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED" desc="Error message in pack extension dialog when the user does not specify a root directory."> + Extension root directory is required. + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID" desc="Error message in pack extension dialog when the user speciifes an invalid root directory."> + Extension root directory is invalid. + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID" desc="Error message in pack extension dialog when the user speciifes an invalid key file."> + Private key file is invalid. + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_SUCCESS_TITLE" desc="Message shown on titlebar of window after successful packing of a new extension."> + Extension Packaging Success + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_SUCCESS_BODY_NEW" desc="Message shown on successful packing of a new extension."> + Created the following files: + +Extension: <ph name="EXTENSION_FILE">$1<ex>c:\users\aa\myextension.crx</ex></ph> +Key File: <ph name="KEY_FILE">$2<ex>c:\users\aa\myextension.pem</ex></ph> + +Keep your key file in a safe place. You will need it to create new versions of your extension. + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_SUCCESS_BODY_UPDATE" desc="Message shown on successful packing of an updated extension."> + Created the extension: + +<ph name="EXTENSION_FILE">$1<ex>c:\users\aa\myextension.crx</ex></ph> + </message> + <message name="IDS_EXTENSION_PACK_DIALOG_FAILURE_TITLE" desc="Message shown on titlebar of window after unsucessful packing of an extension."> + Extension Packaging Failure + </message> <!-- TODO(aa): Remove these old warning messages when all the platforms are switched to the new ones, above. --> <message name="IDS_EXTENSION_PROMPT_WARNING_1" desc="Humorous warning displayed in the extension install prompt that tells users that extensions have access to their computer and private data."> 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 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This comment is only here because changes to resources are not picked up -without changes to the corresponding grd file. aa2 --> +without changes to the corresponding grd file. aa3 --> <grit latest_public_release="0" current_release="1"> <outputs> <output filename="grit/browser_resources.h" type="rc_header"> 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', []); +} </script> </head> <body onload="requestExtensionsData();"> @@ -325,13 +337,13 @@ function loadExtension() { </div> </td> - <td style="min-width:30px"></td> - <td style="min-width:30px"></td> + <td style="min-width:20px"></td> <td valign="top"> <h2>Tools</h2> <div class="sidebar-content"> - <button onclick="loadExtension()">Load Extension from Directory...</button> + <button onclick="loadExtension()">Load Unpacked Extension...</button><br /> + <button onclick="packExtension()">Pack Extension...</button> </div> </td> </tr> 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<PackExtensionJob> { + 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<FilePath::StringType>()); + 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<FilePath>& files, void* params) {}; + virtual void FileSelectionCanceled(void* params) {}; + virtual void FileSelected(const FilePath& path, int index, void* params) { + static_cast<views::Textfield*>(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<SelectFileDialog> file_dialog_; + scoped_refptr<PackExtensionJob> 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', |