1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/webui/extensions/extension_loader_handler.h"
8 #include "base/file_util.h"
9 #include "base/logging.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/extensions/path_util.h"
16 #include "chrome/browser/extensions/unpacked_installer.h"
17 #include "chrome/browser/extensions/zipfile_installer.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/chrome_select_file_policy.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/user_metrics.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_ui.h"
24 #include "content/public/browser/web_ui_data_source.h"
25 #include "extensions/browser/extension_system.h"
26 #include "extensions/browser/file_highlighter.h"
27 #include "extensions/common/constants.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/manifest_constants.h"
30 #include "grit/generated_resources.h"
31 #include "third_party/re2/re2/re2.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/shell_dialogs/select_file_dialog.h"
35 namespace extensions {
39 // Read a file to a string and return.
40 std::string ReadFileToString(const base::FilePath& path) {
42 // This call can fail, but it doesn't matter for our purposes. If it fails,
43 // we simply return an empty string for the manifest, and ignore it.
44 base::ReadFileToString(path, &data);
50 class ExtensionLoaderHandler::FileHelper
51 : public ui::SelectFileDialog::Listener {
53 explicit FileHelper(ExtensionLoaderHandler* loader_handler);
54 virtual ~FileHelper();
56 // Create a FileDialog for the user to select the unpacked extension
61 // ui::SelectFileDialog::Listener implementation.
62 virtual void FileSelected(const base::FilePath& path,
64 void* params) OVERRIDE;
65 virtual void MultiFilesSelected(
66 const std::vector<base::FilePath>& files, void* params) OVERRIDE;
68 // The associated ExtensionLoaderHandler. Weak, but guaranteed to be alive,
69 // as it owns this object.
70 ExtensionLoaderHandler* loader_handler_;
72 // The dialog used to pick a directory when loading an unpacked extension.
73 scoped_refptr<ui::SelectFileDialog> load_extension_dialog_;
75 // The last selected directory, so we can start in the same spot.
76 base::FilePath last_unpacked_directory_;
78 // The title of the dialog.
79 base::string16 title_;
81 DISALLOW_COPY_AND_ASSIGN(FileHelper);
84 ExtensionLoaderHandler::FileHelper::FileHelper(
85 ExtensionLoaderHandler* loader_handler)
86 : loader_handler_(loader_handler),
87 title_(l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY)) {
90 ExtensionLoaderHandler::FileHelper::~FileHelper() {
91 // There may be a pending file dialog; inform it the listener is destroyed so
92 // it doesn't try and call back.
93 if (load_extension_dialog_.get())
94 load_extension_dialog_->ListenerDestroyed();
97 void ExtensionLoaderHandler::FileHelper::ChooseFile() {
98 static const int kFileTypeIndex = 0; // No file type information to index.
99 static const ui::SelectFileDialog::Type kSelectType =
100 ui::SelectFileDialog::SELECT_FOLDER;
102 if (!load_extension_dialog_.get()) {
103 load_extension_dialog_ = ui::SelectFileDialog::Create(
105 new ChromeSelectFilePolicy(
106 loader_handler_->web_ui()->GetWebContents()));
109 load_extension_dialog_->SelectFile(
112 last_unpacked_directory_,
115 base::FilePath::StringType(),
116 loader_handler_->web_ui()->GetWebContents()->GetTopLevelNativeWindow(),
119 content::RecordComputedAction("Options_LoadUnpackedExtension");
122 void ExtensionLoaderHandler::FileHelper::FileSelected(
123 const base::FilePath& path, int index, void* params) {
124 loader_handler_->LoadUnpackedExtensionImpl(path);
127 void ExtensionLoaderHandler::FileHelper::MultiFilesSelected(
128 const std::vector<base::FilePath>& files, void* params) {
132 ExtensionLoaderHandler::ExtensionLoaderHandler(Profile* profile)
134 file_helper_(new FileHelper(this)),
135 extension_error_reporter_observer_(this),
137 weak_ptr_factory_(this) {
139 extension_error_reporter_observer_.Add(ExtensionErrorReporter::GetInstance());
142 ExtensionLoaderHandler::~ExtensionLoaderHandler() {
145 void ExtensionLoaderHandler::GetLocalizedValues(
146 content::WebUIDataSource* source) {
148 "extensionLoadErrorHeading",
149 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_HEADING));
151 "extensionLoadErrorMessage",
152 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_MESSAGE));
154 "extensionLoadErrorRetry",
155 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_RETRY));
157 "extensionLoadErrorGiveUp",
158 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_GIVE_UP));
160 "extensionLoadCouldNotLoadManifest",
161 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_COULD_NOT_LOAD_MANIFEST));
163 "extensionLoadAdditionalFailures",
164 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ADDITIONAL_FAILURES));
167 void ExtensionLoaderHandler::RegisterMessages() {
168 // We observe WebContents in order to detect page refreshes, since notifying
169 // the frontend of load failures must be delayed until the page finishes
170 // loading. We never call Observe(NULL) because this object is constructed
171 // on page load and persists between refreshes.
172 content::WebContentsObserver::Observe(web_ui()->GetWebContents());
174 web_ui()->RegisterMessageCallback(
175 "extensionLoaderLoadUnpacked",
176 base::Bind(&ExtensionLoaderHandler::HandleLoadUnpacked,
177 weak_ptr_factory_.GetWeakPtr()));
178 web_ui()->RegisterMessageCallback(
179 "extensionLoaderRetry",
180 base::Bind(&ExtensionLoaderHandler::HandleRetry,
181 weak_ptr_factory_.GetWeakPtr()));
182 web_ui()->RegisterMessageCallback(
183 "extensionLoaderIgnoreFailure",
184 base::Bind(&ExtensionLoaderHandler::HandleIgnoreFailure,
185 weak_ptr_factory_.GetWeakPtr()));
186 web_ui()->RegisterMessageCallback(
187 "extensionLoaderDisplayFailures",
188 base::Bind(&ExtensionLoaderHandler::HandleDisplayFailures,
189 weak_ptr_factory_.GetWeakPtr()));
192 void ExtensionLoaderHandler::HandleLoadUnpacked(const base::ListValue* args) {
193 DCHECK(args->empty());
194 file_helper_->ChooseFile();
197 void ExtensionLoaderHandler::HandleRetry(const base::ListValue* args) {
198 DCHECK(args->empty());
199 const base::FilePath file_path = failed_paths_.back();
200 failed_paths_.pop_back();
201 LoadUnpackedExtensionImpl(file_path);
204 void ExtensionLoaderHandler::HandleIgnoreFailure(const base::ListValue* args) {
205 DCHECK(args->empty());
206 failed_paths_.pop_back();
209 void ExtensionLoaderHandler::HandleDisplayFailures(
210 const base::ListValue* args) {
211 DCHECK(args->empty());
214 // Notify the frontend of any load failures that were triggered while the
215 // chrome://extensions page was loading.
216 if (!failures_.empty())
217 NotifyFrontendOfFailure();
220 void ExtensionLoaderHandler::LoadUnpackedExtensionImpl(
221 const base::FilePath& file_path) {
222 if (EndsWith(file_path.AsUTF16Unsafe(),
223 base::ASCIIToUTF16(".zip"),
224 false /* case insensitive */)) {
225 scoped_refptr<ZipFileInstaller> installer = ZipFileInstaller::Create(
226 ExtensionSystem::Get(profile_)->extension_service());
228 // We do our own error handling, so we don't want a load failure to trigger
230 installer->set_be_noisy_on_failure(false);
232 installer->LoadFromZipFile(file_path);
234 scoped_refptr<UnpackedInstaller> installer = UnpackedInstaller::Create(
235 ExtensionSystem::Get(profile_)->extension_service());
237 // We do our own error handling, so we don't want a load failure to trigger
239 installer->set_be_noisy_on_failure(false);
241 installer->Load(file_path);
245 void ExtensionLoaderHandler::OnLoadFailure(
246 content::BrowserContext* browser_context,
247 const base::FilePath& file_path,
248 const std::string& error) {
249 // Only show errors from our browser context.
250 if (web_ui()->GetWebContents()->GetBrowserContext() != browser_context)
256 base::StringPrintf("%s Line: (\\d+), column: (\\d+), .*",
257 manifest_errors::kManifestParseError);
258 // If this was a JSON parse error, we can highlight the exact line with the
259 // error. Otherwise, we should still display the manifest (for consistency,
260 // reference, and so that if we ever make this really fancy and add an editor,
263 // This regex call can fail, but if it does, we just don't highlight anything.
264 re2::RE2::FullMatch(error, regex, &line, &column);
266 // This will read the manifest and call AddFailure with the read manifest
268 base::PostTaskAndReplyWithResult(
269 content::BrowserThread::GetBlockingPool(),
271 base::Bind(&ReadFileToString, file_path.Append(kManifestFilename)),
272 base::Bind(&ExtensionLoaderHandler::AddFailure,
273 weak_ptr_factory_.GetWeakPtr(),
279 void ExtensionLoaderHandler::DidStartNavigationToPendingEntry(
281 content::NavigationController::ReloadType reload_type) {
282 // In the event of a page reload, we ensure that the frontend is not notified
283 // until the UI finishes loading, so we set |ui_ready_| to false. This is
284 // balanced in HandleDisplayFailures, which is called when the frontend is
285 // ready to receive failure notifications.
286 if (reload_type != content::NavigationController::NO_RELOAD)
290 void ExtensionLoaderHandler::AddFailure(
291 const base::FilePath& file_path,
292 const std::string& error,
294 const std::string& manifest) {
295 failed_paths_.push_back(file_path);
296 base::FilePath prettified_path = path_util::PrettifyPath(file_path);
298 scoped_ptr<base::DictionaryValue> manifest_value(new base::DictionaryValue());
299 SourceHighlighter highlighter(manifest, line_number);
300 // If the line number is 0, this highlights no regions, but still adds the
302 highlighter.SetHighlightedRegions(manifest_value.get());
304 scoped_ptr<base::DictionaryValue> failure(new base::DictionaryValue());
306 new base::StringValue(prettified_path.LossyDisplayName()));
307 failure->Set("error", new base::StringValue(base::UTF8ToUTF16(error)));
308 failure->Set("manifest", manifest_value.release());
309 failures_.Append(failure.release());
311 // Only notify the frontend if the frontend UI is ready.
313 NotifyFrontendOfFailure();
316 void ExtensionLoaderHandler::NotifyFrontendOfFailure() {
317 web_ui()->CallJavascriptFunction(
318 "extensions.ExtensionLoader.notifyLoadFailed",
323 } // namespace extensions