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.
4
5 #include "chrome/browser/ui/webui/extensions/extension_loader_handler.h"
6
7 #include "base/bind.h"
8 #include "base/files/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 "chrome/grit/generated_resources.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/user_metrics.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/browser/web_ui.h"
25 #include "content/public/browser/web_ui_data_source.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/browser/file_highlighter.h"
28 #include "extensions/common/constants.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/manifest_constants.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"
34
35 namespace extensions {
36
37 namespace {
38
39 // Read a file to a string and return.
ReadFileToString(const base::FilePath & path)40 std::string ReadFileToString(const base::FilePath& path) {
41 std::string data;
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);
45 return data;
46 }
47
48 } // namespace
49
50 class ExtensionLoaderHandler::FileHelper
51 : public ui::SelectFileDialog::Listener {
52 public:
53 explicit FileHelper(ExtensionLoaderHandler* loader_handler);
54 virtual ~FileHelper();
55
56 // Create a FileDialog for the user to select the unpacked extension
57 // directory.
58 void ChooseFile();
59
60 private:
61 // ui::SelectFileDialog::Listener implementation.
62 virtual void FileSelected(const base::FilePath& path,
63 int index,
64 void* params) OVERRIDE;
65 virtual void MultiFilesSelected(
66 const std::vector<base::FilePath>& files, void* params) OVERRIDE;
67
68 // The associated ExtensionLoaderHandler. Weak, but guaranteed to be alive,
69 // as it owns this object.
70 ExtensionLoaderHandler* loader_handler_;
71
72 // The dialog used to pick a directory when loading an unpacked extension.
73 scoped_refptr<ui::SelectFileDialog> load_extension_dialog_;
74
75 // The last selected directory, so we can start in the same spot.
76 base::FilePath last_unpacked_directory_;
77
78 // The title of the dialog.
79 base::string16 title_;
80
81 DISALLOW_COPY_AND_ASSIGN(FileHelper);
82 };
83
FileHelper(ExtensionLoaderHandler * loader_handler)84 ExtensionLoaderHandler::FileHelper::FileHelper(
85 ExtensionLoaderHandler* loader_handler)
86 : loader_handler_(loader_handler),
87 title_(l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY)) {
88 }
89
~FileHelper()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();
95 }
96
ChooseFile()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;
101
102 if (!load_extension_dialog_.get()) {
103 load_extension_dialog_ = ui::SelectFileDialog::Create(
104 this,
105 new ChromeSelectFilePolicy(
106 loader_handler_->web_ui()->GetWebContents()));
107 }
108
109 load_extension_dialog_->SelectFile(
110 kSelectType,
111 title_,
112 last_unpacked_directory_,
113 NULL,
114 kFileTypeIndex,
115 base::FilePath::StringType(),
116 loader_handler_->web_ui()->GetWebContents()->GetTopLevelNativeWindow(),
117 NULL);
118
119 content::RecordComputedAction("Options_LoadUnpackedExtension");
120 }
121
FileSelected(const base::FilePath & path,int index,void * params)122 void ExtensionLoaderHandler::FileHelper::FileSelected(
123 const base::FilePath& path, int index, void* params) {
124 loader_handler_->LoadUnpackedExtensionImpl(path);
125 }
126
MultiFilesSelected(const std::vector<base::FilePath> & files,void * params)127 void ExtensionLoaderHandler::FileHelper::MultiFilesSelected(
128 const std::vector<base::FilePath>& files, void* params) {
129 NOTREACHED();
130 }
131
ExtensionLoaderHandler(Profile * profile)132 ExtensionLoaderHandler::ExtensionLoaderHandler(Profile* profile)
133 : profile_(profile),
134 file_helper_(new FileHelper(this)),
135 extension_error_reporter_observer_(this),
136 ui_ready_(false),
137 weak_ptr_factory_(this) {
138 DCHECK(profile_);
139 extension_error_reporter_observer_.Add(ExtensionErrorReporter::GetInstance());
140 }
141
~ExtensionLoaderHandler()142 ExtensionLoaderHandler::~ExtensionLoaderHandler() {
143 }
144
GetLocalizedValues(content::WebUIDataSource * source)145 void ExtensionLoaderHandler::GetLocalizedValues(
146 content::WebUIDataSource* source) {
147 source->AddString(
148 "extensionLoadErrorHeading",
149 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_HEADING));
150 source->AddString(
151 "extensionLoadErrorMessage",
152 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_MESSAGE));
153 source->AddString(
154 "extensionLoadErrorRetry",
155 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_RETRY));
156 source->AddString(
157 "extensionLoadErrorGiveUp",
158 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ERROR_GIVE_UP));
159 source->AddString(
160 "extensionLoadCouldNotLoadManifest",
161 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_COULD_NOT_LOAD_MANIFEST));
162 source->AddString(
163 "extensionLoadAdditionalFailures",
164 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_ADDITIONAL_FAILURES));
165 }
166
RegisterMessages()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());
173
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()));
190 }
191
HandleLoadUnpacked(const base::ListValue * args)192 void ExtensionLoaderHandler::HandleLoadUnpacked(const base::ListValue* args) {
193 DCHECK(args->empty());
194 file_helper_->ChooseFile();
195 }
196
HandleRetry(const base::ListValue * args)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);
202 }
203
HandleIgnoreFailure(const base::ListValue * args)204 void ExtensionLoaderHandler::HandleIgnoreFailure(const base::ListValue* args) {
205 DCHECK(args->empty());
206 failed_paths_.pop_back();
207 }
208
HandleDisplayFailures(const base::ListValue * args)209 void ExtensionLoaderHandler::HandleDisplayFailures(
210 const base::ListValue* args) {
211 DCHECK(args->empty());
212 ui_ready_ = true;
213
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();
218 }
219
LoadUnpackedExtensionImpl(const base::FilePath & file_path)220 void ExtensionLoaderHandler::LoadUnpackedExtensionImpl(
221 const base::FilePath& file_path) {
222 scoped_refptr<UnpackedInstaller> installer = UnpackedInstaller::Create(
223 ExtensionSystem::Get(profile_)->extension_service());
224
225 // We do our own error handling, so we don't want a load failure to trigger
226 // a dialog.
227 installer->set_be_noisy_on_failure(false);
228
229 installer->Load(file_path);
230 }
231
OnLoadFailure(content::BrowserContext * browser_context,const base::FilePath & file_path,const std::string & error)232 void ExtensionLoaderHandler::OnLoadFailure(
233 content::BrowserContext* browser_context,
234 const base::FilePath& file_path,
235 const std::string& error) {
236 // Only show errors from our browser context.
237 if (web_ui()->GetWebContents()->GetBrowserContext() != browser_context)
238 return;
239
240 size_t line = 0u;
241 size_t column = 0u;
242 std::string regex =
243 base::StringPrintf("%s Line: (\\d+), column: (\\d+), .*",
244 manifest_errors::kManifestParseError);
245 // If this was a JSON parse error, we can highlight the exact line with the
246 // error. Otherwise, we should still display the manifest (for consistency,
247 // reference, and so that if we ever make this really fancy and add an editor,
248 // it's ready).
249 //
250 // This regex call can fail, but if it does, we just don't highlight anything.
251 re2::RE2::FullMatch(error, regex, &line, &column);
252
253 // This will read the manifest and call AddFailure with the read manifest
254 // contents.
255 base::PostTaskAndReplyWithResult(
256 content::BrowserThread::GetBlockingPool(),
257 FROM_HERE,
258 base::Bind(&ReadFileToString, file_path.Append(kManifestFilename)),
259 base::Bind(&ExtensionLoaderHandler::AddFailure,
260 weak_ptr_factory_.GetWeakPtr(),
261 file_path,
262 error,
263 line));
264 }
265
DidStartNavigationToPendingEntry(const GURL & url,content::NavigationController::ReloadType reload_type)266 void ExtensionLoaderHandler::DidStartNavigationToPendingEntry(
267 const GURL& url,
268 content::NavigationController::ReloadType reload_type) {
269 // In the event of a page reload, we ensure that the frontend is not notified
270 // until the UI finishes loading, so we set |ui_ready_| to false. This is
271 // balanced in HandleDisplayFailures, which is called when the frontend is
272 // ready to receive failure notifications.
273 if (reload_type != content::NavigationController::NO_RELOAD)
274 ui_ready_ = false;
275 }
276
AddFailure(const base::FilePath & file_path,const std::string & error,size_t line_number,const std::string & manifest)277 void ExtensionLoaderHandler::AddFailure(
278 const base::FilePath& file_path,
279 const std::string& error,
280 size_t line_number,
281 const std::string& manifest) {
282 failed_paths_.push_back(file_path);
283 base::FilePath prettified_path = path_util::PrettifyPath(file_path);
284
285 scoped_ptr<base::DictionaryValue> manifest_value(new base::DictionaryValue());
286 SourceHighlighter highlighter(manifest, line_number);
287 // If the line number is 0, this highlights no regions, but still adds the
288 // full manifest.
289 highlighter.SetHighlightedRegions(manifest_value.get());
290
291 scoped_ptr<base::DictionaryValue> failure(new base::DictionaryValue());
292 failure->Set("path",
293 new base::StringValue(prettified_path.LossyDisplayName()));
294 failure->Set("error", new base::StringValue(base::UTF8ToUTF16(error)));
295 failure->Set("manifest", manifest_value.release());
296 failures_.Append(failure.release());
297
298 // Only notify the frontend if the frontend UI is ready.
299 if (ui_ready_)
300 NotifyFrontendOfFailure();
301 }
302
NotifyFrontendOfFailure()303 void ExtensionLoaderHandler::NotifyFrontendOfFailure() {
304 web_ui()->CallJavascriptFunction(
305 "extensions.ExtensionLoader.notifyLoadFailed",
306 failures_);
307 failures_.Clear();
308 }
309
310 } // namespace extensions
311