• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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