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 35namespace extensions { 36 37namespace { 38 39// Read a file to a string and return. 40std::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 50class 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 84ExtensionLoaderHandler::FileHelper::FileHelper( 85 ExtensionLoaderHandler* loader_handler) 86 : loader_handler_(loader_handler), 87 title_(l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY)) { 88} 89 90ExtensionLoaderHandler::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 97void 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 122void ExtensionLoaderHandler::FileHelper::FileSelected( 123 const base::FilePath& path, int index, void* params) { 124 loader_handler_->LoadUnpackedExtensionImpl(path); 125} 126 127void ExtensionLoaderHandler::FileHelper::MultiFilesSelected( 128 const std::vector<base::FilePath>& files, void* params) { 129 NOTREACHED(); 130} 131 132ExtensionLoaderHandler::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 142ExtensionLoaderHandler::~ExtensionLoaderHandler() { 143} 144 145void 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 167void 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 192void ExtensionLoaderHandler::HandleLoadUnpacked(const base::ListValue* args) { 193 DCHECK(args->empty()); 194 file_helper_->ChooseFile(); 195} 196 197void 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 204void ExtensionLoaderHandler::HandleIgnoreFailure(const base::ListValue* args) { 205 DCHECK(args->empty()); 206 failed_paths_.pop_back(); 207} 208 209void 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 220void 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 232void 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 266void 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 277void 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 303void ExtensionLoaderHandler::NotifyFrontendOfFailure() { 304 web_ui()->CallJavascriptFunction( 305 "extensions.ExtensionLoader.notifyLoadFailed", 306 failures_); 307 failures_.Clear(); 308} 309 310} // namespace extensions 311