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/extensions/external_install_error.h" 6 7#include "base/bind.h" 8#include "base/strings/utf_string_conversions.h" 9#include "chrome/app/chrome_command_ids.h" 10#include "chrome/browser/extensions/extension_service.h" 11#include "chrome/browser/extensions/external_install_manager.h" 12#include "chrome/browser/extensions/webstore_data_fetcher.h" 13#include "chrome/browser/profiles/profile.h" 14#include "chrome/browser/ui/browser.h" 15#include "chrome/browser/ui/browser_finder.h" 16#include "chrome/browser/ui/global_error/global_error.h" 17#include "chrome/browser/ui/global_error/global_error_service.h" 18#include "chrome/browser/ui/global_error/global_error_service_factory.h" 19#include "chrome/browser/ui/tabs/tab_strip_model.h" 20#include "chrome/grit/generated_resources.h" 21#include "extensions/browser/extension_registry.h" 22#include "extensions/browser/extension_system.h" 23#include "extensions/browser/uninstall_reason.h" 24#include "extensions/common/constants.h" 25#include "extensions/common/extension.h" 26#include "ui/base/l10n/l10n_util.h" 27#include "ui/gfx/image/image.h" 28#include "ui/gfx/image/image_skia_operations.h" 29 30namespace extensions { 31 32namespace { 33 34// Return the menu label for a global error. 35base::string16 GetMenuItemLabel(const Extension* extension) { 36 if (!extension) 37 return base::string16(); 38 39 int id = -1; 40 if (extension->is_app()) 41 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP; 42 else if (extension->is_theme()) 43 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME; 44 else 45 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION; 46 47 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension->name())); 48} 49 50// A global error that spawns a dialog when the menu item is clicked. 51class ExternalInstallMenuAlert : public GlobalError { 52 public: 53 explicit ExternalInstallMenuAlert(ExternalInstallError* error); 54 virtual ~ExternalInstallMenuAlert(); 55 56 private: 57 // GlobalError implementation. 58 virtual Severity GetSeverity() OVERRIDE; 59 virtual bool HasMenuItem() OVERRIDE; 60 virtual int MenuItemCommandID() OVERRIDE; 61 virtual base::string16 MenuItemLabel() OVERRIDE; 62 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE; 63 virtual bool HasBubbleView() OVERRIDE; 64 virtual bool HasShownBubbleView() OVERRIDE; 65 virtual void ShowBubbleView(Browser* browser) OVERRIDE; 66 virtual GlobalErrorBubbleViewBase* GetBubbleView() OVERRIDE; 67 68 // The owning ExternalInstallError. 69 ExternalInstallError* error_; 70 71 DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert); 72}; 73 74// A global error that spawns a bubble when the menu item is clicked. 75class ExternalInstallBubbleAlert : public GlobalErrorWithStandardBubble { 76 public: 77 explicit ExternalInstallBubbleAlert(ExternalInstallError* error, 78 ExtensionInstallPrompt::Prompt* prompt); 79 virtual ~ExternalInstallBubbleAlert(); 80 81 private: 82 // GlobalError implementation. 83 virtual Severity GetSeverity() OVERRIDE; 84 virtual bool HasMenuItem() OVERRIDE; 85 virtual int MenuItemCommandID() OVERRIDE; 86 virtual base::string16 MenuItemLabel() OVERRIDE; 87 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE; 88 89 // GlobalErrorWithStandardBubble implementation. 90 virtual gfx::Image GetBubbleViewIcon() OVERRIDE; 91 virtual base::string16 GetBubbleViewTitle() OVERRIDE; 92 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE; 93 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE; 94 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE; 95 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE; 96 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE; 97 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE; 98 99 // The owning ExternalInstallError. 100 ExternalInstallError* error_; 101 102 // The Prompt with all information, which we then use to populate the bubble. 103 ExtensionInstallPrompt::Prompt* prompt_; 104 105 DISALLOW_COPY_AND_ASSIGN(ExternalInstallBubbleAlert); 106}; 107 108//////////////////////////////////////////////////////////////////////////////// 109// ExternalInstallMenuAlert 110 111ExternalInstallMenuAlert::ExternalInstallMenuAlert(ExternalInstallError* error) 112 : error_(error) { 113} 114 115ExternalInstallMenuAlert::~ExternalInstallMenuAlert() { 116} 117 118GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() { 119 return SEVERITY_LOW; 120} 121 122bool ExternalInstallMenuAlert::HasMenuItem() { 123 return true; 124} 125 126int ExternalInstallMenuAlert::MenuItemCommandID() { 127 return IDC_EXTERNAL_EXTENSION_ALERT; 128} 129 130base::string16 ExternalInstallMenuAlert::MenuItemLabel() { 131 return GetMenuItemLabel(error_->GetExtension()); 132} 133 134void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) { 135 error_->ShowDialog(browser); 136} 137 138bool ExternalInstallMenuAlert::HasBubbleView() { 139 return false; 140} 141 142bool ExternalInstallMenuAlert::HasShownBubbleView() { 143 NOTREACHED(); 144 return true; 145} 146 147void ExternalInstallMenuAlert::ShowBubbleView(Browser* browser) { 148 NOTREACHED(); 149} 150 151GlobalErrorBubbleViewBase* ExternalInstallMenuAlert::GetBubbleView() { 152 return NULL; 153} 154 155//////////////////////////////////////////////////////////////////////////////// 156// ExternalInstallBubbleAlert 157 158ExternalInstallBubbleAlert::ExternalInstallBubbleAlert( 159 ExternalInstallError* error, 160 ExtensionInstallPrompt::Prompt* prompt) 161 : error_(error), prompt_(prompt) { 162 DCHECK(error_); 163 DCHECK(prompt_); 164} 165 166ExternalInstallBubbleAlert::~ExternalInstallBubbleAlert() { 167} 168 169GlobalError::Severity ExternalInstallBubbleAlert::GetSeverity() { 170 return SEVERITY_LOW; 171} 172 173bool ExternalInstallBubbleAlert::HasMenuItem() { 174 return true; 175} 176 177int ExternalInstallBubbleAlert::MenuItemCommandID() { 178 return IDC_EXTERNAL_EXTENSION_ALERT; 179} 180 181base::string16 ExternalInstallBubbleAlert::MenuItemLabel() { 182 return GetMenuItemLabel(error_->GetExtension()); 183} 184 185void ExternalInstallBubbleAlert::ExecuteMenuItem(Browser* browser) { 186 ShowBubbleView(browser); 187} 188 189gfx::Image ExternalInstallBubbleAlert::GetBubbleViewIcon() { 190 if (prompt_->icon().IsEmpty()) 191 return GlobalErrorWithStandardBubble::GetBubbleViewIcon(); 192 // Scale icon to a reasonable size. 193 return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage( 194 *prompt_->icon().ToImageSkia(), 195 skia::ImageOperations::RESIZE_BEST, 196 gfx::Size(extension_misc::EXTENSION_ICON_SMALL, 197 extension_misc::EXTENSION_ICON_SMALL))); 198} 199 200base::string16 ExternalInstallBubbleAlert::GetBubbleViewTitle() { 201 return prompt_->GetDialogTitle(); 202} 203 204std::vector<base::string16> 205ExternalInstallBubbleAlert::GetBubbleViewMessages() { 206 ExtensionInstallPrompt::PermissionsType regular_permissions = 207 ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS; 208 ExtensionInstallPrompt::PermissionsType withheld_permissions = 209 ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS; 210 211 std::vector<base::string16> messages; 212 messages.push_back(prompt_->GetHeading()); 213 if (prompt_->GetPermissionCount(regular_permissions)) { 214 messages.push_back(prompt_->GetPermissionsHeading(regular_permissions)); 215 for (size_t i = 0; i < prompt_->GetPermissionCount(regular_permissions); 216 ++i) { 217 messages.push_back(l10n_util::GetStringFUTF16( 218 IDS_EXTENSION_PERMISSION_LINE, 219 prompt_->GetPermission(i, regular_permissions))); 220 } 221 } 222 if (prompt_->GetPermissionCount(withheld_permissions)) { 223 messages.push_back(prompt_->GetPermissionsHeading(withheld_permissions)); 224 for (size_t i = 0; i < prompt_->GetPermissionCount(withheld_permissions); 225 ++i) { 226 messages.push_back(l10n_util::GetStringFUTF16( 227 IDS_EXTENSION_PERMISSION_LINE, 228 prompt_->GetPermission(i, withheld_permissions))); 229 } 230 } 231 // TODO(yoz): OAuth issue advice? 232 return messages; 233} 234 235base::string16 ExternalInstallBubbleAlert::GetBubbleViewAcceptButtonLabel() { 236 return prompt_->GetAcceptButtonLabel(); 237} 238 239base::string16 ExternalInstallBubbleAlert::GetBubbleViewCancelButtonLabel() { 240 return prompt_->GetAbortButtonLabel(); 241} 242 243void ExternalInstallBubbleAlert::OnBubbleViewDidClose(Browser* browser) { 244} 245 246void ExternalInstallBubbleAlert::BubbleViewAcceptButtonPressed( 247 Browser* browser) { 248 error_->InstallUIProceed(); 249} 250 251void ExternalInstallBubbleAlert::BubbleViewCancelButtonPressed( 252 Browser* browser) { 253 error_->InstallUIAbort(true); 254} 255 256} // namespace 257 258//////////////////////////////////////////////////////////////////////////////// 259// ExternalInstallError 260 261ExternalInstallError::ExternalInstallError( 262 content::BrowserContext* browser_context, 263 const std::string& extension_id, 264 AlertType alert_type, 265 ExternalInstallManager* manager) 266 : browser_context_(browser_context), 267 extension_id_(extension_id), 268 alert_type_(alert_type), 269 manager_(manager), 270 error_service_(GlobalErrorServiceFactory::GetForProfile( 271 Profile::FromBrowserContext(browser_context_))), 272 weak_factory_(this) { 273 prompt_ = new ExtensionInstallPrompt::Prompt( 274 ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT); 275 276 webstore_data_fetcher_.reset(new WebstoreDataFetcher( 277 this, browser_context_->GetRequestContext(), GURL(), extension_id_)); 278 webstore_data_fetcher_->Start(); 279} 280 281ExternalInstallError::~ExternalInstallError() { 282 if (global_error_.get()) 283 error_service_->RemoveGlobalError(global_error_.get()); 284} 285 286void ExternalInstallError::InstallUIProceed() { 287 const Extension* extension = GetExtension(); 288 if (extension) { 289 ExtensionSystem::Get(browser_context_) 290 ->extension_service() 291 ->GrantPermissionsAndEnableExtension(extension); 292 // Since the manager listens for the extension to be loaded, this will 293 // remove the error... 294 } else { 295 // ... Otherwise we have to do it explicitly. 296 manager_->RemoveExternalInstallError(); 297 } 298} 299 300void ExternalInstallError::InstallUIAbort(bool user_initiated) { 301 if (user_initiated && GetExtension()) { 302 ExtensionSystem::Get(browser_context_) 303 ->extension_service() 304 ->UninstallExtension(extension_id_, 305 extensions::UNINSTALL_REASON_INSTALL_CANCELED, 306 base::Bind(&base::DoNothing), 307 NULL); // Ignore error. 308 // Since the manager listens for the extension to be removed, this will 309 // remove the error... 310 } else { 311 // ... Otherwise we have to do it explicitly. 312 manager_->RemoveExternalInstallError(); 313 } 314} 315 316void ExternalInstallError::ShowDialog(Browser* browser) { 317 DCHECK(install_ui_.get()); 318 DCHECK(prompt_.get()); 319 DCHECK(browser); 320 content::WebContents* web_contents = NULL; 321 web_contents = browser->tab_strip_model()->GetActiveWebContents(); 322 ExtensionInstallPrompt::ShowParams params(web_contents); 323 ExtensionInstallPrompt::GetDefaultShowDialogCallback().Run( 324 params, this, prompt_); 325} 326 327const Extension* ExternalInstallError::GetExtension() const { 328 return ExtensionRegistry::Get(browser_context_) 329 ->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING); 330} 331 332void ExternalInstallError::OnWebstoreRequestFailure() { 333 OnFetchComplete(); 334} 335 336void ExternalInstallError::OnWebstoreResponseParseSuccess( 337 scoped_ptr<base::DictionaryValue> webstore_data) { 338 std::string localized_user_count; 339 double average_rating = 0; 340 int rating_count = 0; 341 if (!webstore_data->GetString(kUsersKey, &localized_user_count) || 342 !webstore_data->GetDouble(kAverageRatingKey, &average_rating) || 343 !webstore_data->GetInteger(kRatingCountKey, &rating_count)) { 344 // If we don't get a valid webstore response, short circuit, and continue 345 // to show a prompt without webstore data. 346 OnFetchComplete(); 347 return; 348 } 349 350 bool show_user_count = true; 351 webstore_data->GetBoolean(kShowUserCountKey, &show_user_count); 352 353 prompt_->SetWebstoreData( 354 localized_user_count, show_user_count, average_rating, rating_count); 355 OnFetchComplete(); 356} 357 358void ExternalInstallError::OnWebstoreResponseParseFailure( 359 const std::string& error) { 360 OnFetchComplete(); 361} 362 363void ExternalInstallError::OnFetchComplete() { 364 // Create a new ExtensionInstallPrompt. We pass in NULL for the UI 365 // components because we display at a later point, and don't want 366 // to pass ones which may be invalidated. 367 install_ui_.reset( 368 new ExtensionInstallPrompt(Profile::FromBrowserContext(browser_context_), 369 NULL, // NULL native window. 370 NULL)); // NULL navigator. 371 372 install_ui_->ConfirmExternalInstall( 373 this, 374 GetExtension(), 375 base::Bind(&ExternalInstallError::OnDialogReady, 376 weak_factory_.GetWeakPtr()), 377 prompt_); 378} 379 380void ExternalInstallError::OnDialogReady( 381 const ExtensionInstallPrompt::ShowParams& show_params, 382 ExtensionInstallPrompt::Delegate* prompt_delegate, 383 scoped_refptr<ExtensionInstallPrompt::Prompt> prompt) { 384 DCHECK_EQ(this, prompt_delegate); 385 prompt_ = prompt; 386 387 if (alert_type_ == BUBBLE_ALERT) { 388 global_error_.reset(new ExternalInstallBubbleAlert(this, prompt_.get())); 389 error_service_->AddGlobalError(global_error_.get()); 390 391 Browser* browser = 392 chrome::FindTabbedBrowser(Profile::FromBrowserContext(browser_context_), 393 true, 394 chrome::GetActiveDesktop()); 395 if (browser) 396 global_error_->ShowBubbleView(browser); 397 } else { 398 DCHECK(alert_type_ == MENU_ALERT); 399 global_error_.reset(new ExternalInstallMenuAlert(this)); 400 error_service_->AddGlobalError(global_error_.get()); 401 } 402} 403 404} // namespace extensions 405