1// Copyright (c) 2012 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_ui.h" 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/lazy_instance.h" 11#include "base/memory/ref_counted.h" 12#include "base/memory/scoped_ptr.h" 13#include "base/message_loop/message_loop.h" 14#include "base/metrics/histogram.h" 15#include "base/strings/utf_string_conversions.h" 16#include "chrome/app/chrome_command_ids.h" 17#include "chrome/browser/chrome_notification_types.h" 18#include "chrome/browser/extensions/extension_install_prompt.h" 19#include "chrome/browser/extensions/extension_install_ui.h" 20#include "chrome/browser/extensions/extension_service.h" 21#include "chrome/browser/extensions/extension_uninstall_dialog.h" 22#include "chrome/browser/profiles/profile.h" 23#include "chrome/browser/ui/browser.h" 24#include "chrome/browser/ui/browser_finder.h" 25#include "chrome/browser/ui/global_error/global_error.h" 26#include "chrome/browser/ui/global_error/global_error_service.h" 27#include "chrome/browser/ui/global_error/global_error_service_factory.h" 28#include "chrome/browser/ui/host_desktop.h" 29#include "chrome/common/extensions/extension_constants.h" 30#include "chrome/common/extensions/manifest_url_handler.h" 31#include "content/public/browser/notification_details.h" 32#include "content/public/browser/notification_observer.h" 33#include "content/public/browser/notification_registrar.h" 34#include "content/public/browser/notification_source.h" 35#include "extensions/common/extension.h" 36#include "grit/chromium_strings.h" 37#include "grit/generated_resources.h" 38#include "grit/theme_resources.h" 39#include "ui/base/l10n/l10n_util.h" 40#include "ui/gfx/image/image.h" 41#include "ui/gfx/image/image_skia_operations.h" 42#include "ui/gfx/size.h" 43 44namespace extensions { 45 46namespace { 47 48// Whether the external extension can use the streamlined bubble install flow. 49bool UseBubbleInstall(const Extension* extension, bool is_new_profile) { 50 return ManifestURL::UpdatesFromGallery(extension) && !is_new_profile; 51} 52 53} // namespace 54 55static const int kMenuCommandId = IDC_EXTERNAL_EXTENSION_ALERT; 56 57class ExternalInstallGlobalError; 58 59// TODO(mpcomplete): Get rid of the refcounting on this class, or document 60// why it's necessary. Will do after refactoring to merge back with 61// ExtensionDisabledDialogDelegate. 62class ExternalInstallDialogDelegate 63 : public ExtensionInstallPrompt::Delegate, 64 public base::RefCountedThreadSafe<ExternalInstallDialogDelegate> { 65 public: 66 ExternalInstallDialogDelegate(Browser* browser, 67 ExtensionService* service, 68 const Extension* extension, 69 bool use_global_error); 70 71 Browser* browser() { return browser_; } 72 73 private: 74 friend class base::RefCountedThreadSafe<ExternalInstallDialogDelegate>; 75 friend class ExternalInstallGlobalError; 76 77 virtual ~ExternalInstallDialogDelegate(); 78 79 // ExtensionInstallPrompt::Delegate: 80 virtual void InstallUIProceed() OVERRIDE; 81 virtual void InstallUIAbort(bool user_initiated) OVERRIDE; 82 83 // The UI for showing the install dialog when enabling. 84 scoped_ptr<ExtensionInstallPrompt> install_ui_; 85 86 Browser* browser_; 87 base::WeakPtr<ExtensionService> service_weak_; 88 const std::string extension_id_; 89}; 90 91// Only shows a menu item, no bubble. Clicking the menu item shows 92// an external install dialog. 93class ExternalInstallMenuAlert : public GlobalErrorWithStandardBubble, 94 public content::NotificationObserver { 95 public: 96 ExternalInstallMenuAlert(ExtensionService* service, 97 const Extension* extension); 98 virtual ~ExternalInstallMenuAlert(); 99 100 const Extension* extension() const { return extension_; } 101 102 // GlobalError implementation. 103 virtual Severity GetSeverity() OVERRIDE; 104 virtual bool HasMenuItem() OVERRIDE; 105 virtual int MenuItemCommandID() OVERRIDE; 106 virtual base::string16 MenuItemLabel() OVERRIDE; 107 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE; 108 virtual bool HasBubbleView() OVERRIDE; 109 virtual base::string16 GetBubbleViewTitle() OVERRIDE; 110 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE; 111 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE; 112 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE; 113 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE; 114 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE; 115 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE; 116 117 // content::NotificationObserver implementation. 118 virtual void Observe(int type, 119 const content::NotificationSource& source, 120 const content::NotificationDetails& details) OVERRIDE; 121 122 protected: 123 ExtensionService* service_; 124 const Extension* extension_; 125 content::NotificationRegistrar registrar_; 126}; 127 128// Shows a menu item and a global error bubble, replacing the install dialog. 129class ExternalInstallGlobalError : public ExternalInstallMenuAlert { 130 public: 131 ExternalInstallGlobalError(ExtensionService* service, 132 const Extension* extension, 133 ExternalInstallDialogDelegate* delegate, 134 const ExtensionInstallPrompt::Prompt& prompt); 135 virtual ~ExternalInstallGlobalError(); 136 137 virtual void ExecuteMenuItem(Browser* browser) OVERRIDE; 138 virtual bool HasBubbleView() OVERRIDE; 139 virtual gfx::Image GetBubbleViewIcon() OVERRIDE; 140 virtual base::string16 GetBubbleViewTitle() OVERRIDE; 141 virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE; 142 virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE; 143 virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE; 144 virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE; 145 virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE; 146 virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE; 147 148 protected: 149 // Ref-counted, but needs to be disposed of if we are dismissed without 150 // having been clicked (perhaps because the user enabled the extension 151 // manually). 152 ExternalInstallDialogDelegate* delegate_; 153 const ExtensionInstallPrompt::Prompt* prompt_; 154}; 155 156static void CreateExternalInstallGlobalError( 157 base::WeakPtr<ExtensionService> service, 158 const std::string& extension_id, 159 const ExtensionInstallPrompt::ShowParams& show_params, 160 ExtensionInstallPrompt::Delegate* prompt_delegate, 161 const ExtensionInstallPrompt::Prompt& prompt) { 162 if (!service.get()) 163 return; 164 const Extension* extension = service->GetInstalledExtension(extension_id); 165 if (!extension) 166 return; 167 GlobalErrorService* error_service = 168 GlobalErrorServiceFactory::GetForProfile(service->profile()); 169 if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId)) 170 return; 171 172 ExternalInstallDialogDelegate* delegate = 173 static_cast<ExternalInstallDialogDelegate*>(prompt_delegate); 174 ExternalInstallGlobalError* error_bubble = new ExternalInstallGlobalError( 175 service.get(), extension, delegate, prompt); 176 error_service->AddGlobalError(error_bubble); 177 // Show bubble immediately if possible. 178 if (delegate->browser()) 179 error_bubble->ShowBubbleView(delegate->browser()); 180} 181 182static void ShowExternalInstallDialog( 183 ExtensionService* service, 184 Browser* browser, 185 const Extension* extension) { 186 // This object manages its own lifetime. 187 new ExternalInstallDialogDelegate(browser, service, extension, false); 188} 189 190// ExternalInstallDialogDelegate -------------------------------------------- 191 192ExternalInstallDialogDelegate::ExternalInstallDialogDelegate( 193 Browser* browser, 194 ExtensionService* service, 195 const Extension* extension, 196 bool use_global_error) 197 : browser_(browser), 198 service_weak_(service->AsWeakPtr()), 199 extension_id_(extension->id()) { 200 AddRef(); // Balanced in Proceed or Abort. 201 202 install_ui_.reset( 203 ExtensionInstallUI::CreateInstallPromptWithBrowser(browser)); 204 205 const ExtensionInstallPrompt::ShowDialogCallback callback = 206 use_global_error ? 207 base::Bind(&CreateExternalInstallGlobalError, 208 service_weak_, extension_id_) : 209 ExtensionInstallPrompt::GetDefaultShowDialogCallback(); 210 install_ui_->ConfirmExternalInstall(this, extension, callback); 211} 212 213ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() { 214} 215 216void ExternalInstallDialogDelegate::InstallUIProceed() { 217 if (!service_weak_.get()) 218 return; 219 const Extension* extension = 220 service_weak_->GetInstalledExtension(extension_id_); 221 if (!extension) 222 return; 223 service_weak_->GrantPermissionsAndEnableExtension(extension); 224 Release(); 225} 226 227void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated) { 228 if (!service_weak_.get()) 229 return; 230 const Extension* extension = 231 service_weak_->GetInstalledExtension(extension_id_); 232 if (!extension) 233 return; 234 service_weak_->UninstallExtension(extension_id_, false, NULL); 235 Release(); 236} 237 238// ExternalInstallMenuAlert ------------------------------------------------- 239 240ExternalInstallMenuAlert::ExternalInstallMenuAlert( 241 ExtensionService* service, 242 const Extension* extension) 243 : service_(service), 244 extension_(extension) { 245 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 246 content::Source<Profile>(service->profile())); 247 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED, 248 content::Source<Profile>(service->profile())); 249} 250 251ExternalInstallMenuAlert::~ExternalInstallMenuAlert() { 252} 253 254GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() { 255 return SEVERITY_LOW; 256} 257 258bool ExternalInstallMenuAlert::HasMenuItem() { 259 return true; 260} 261 262int ExternalInstallMenuAlert::MenuItemCommandID() { 263 return kMenuCommandId; 264} 265 266base::string16 ExternalInstallMenuAlert::MenuItemLabel() { 267 int id = -1; 268 if (extension_->is_app()) 269 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP; 270 else if (extension_->is_theme()) 271 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME; 272 else 273 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION; 274 return l10n_util::GetStringFUTF16(id, UTF8ToUTF16(extension_->name())); 275} 276 277void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) { 278 ShowExternalInstallDialog(service_, browser, extension_); 279} 280 281bool ExternalInstallMenuAlert::HasBubbleView() { 282 return false; 283} 284base::string16 ExternalInstallMenuAlert::GetBubbleViewTitle() { 285 return base::string16(); 286} 287 288std::vector<base::string16> ExternalInstallMenuAlert::GetBubbleViewMessages() { 289 return std::vector<base::string16>(); 290} 291 292base::string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() { 293 return base::string16(); 294} 295 296base::string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() { 297 return base::string16(); 298} 299 300void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) { 301 NOTREACHED(); 302} 303 304void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed( 305 Browser* browser) { 306 NOTREACHED(); 307} 308 309void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed( 310 Browser* browser) { 311 NOTREACHED(); 312} 313 314void ExternalInstallMenuAlert::Observe( 315 int type, 316 const content::NotificationSource& source, 317 const content::NotificationDetails& details) { 318 // The error is invalidated if the extension has been loaded or removed. 319 DCHECK(type == chrome::NOTIFICATION_EXTENSION_LOADED || 320 type == chrome::NOTIFICATION_EXTENSION_REMOVED); 321 const Extension* extension = content::Details<const Extension>(details).ptr(); 322 if (extension != extension_) 323 return; 324 GlobalErrorService* error_service = 325 GlobalErrorServiceFactory::GetForProfile(service_->profile()); 326 error_service->RemoveGlobalError(this); 327 service_->AcknowledgeExternalExtension(extension_->id()); 328 delete this; 329} 330 331// ExternalInstallGlobalError ----------------------------------------------- 332 333ExternalInstallGlobalError::ExternalInstallGlobalError( 334 ExtensionService* service, 335 const Extension* extension, 336 ExternalInstallDialogDelegate* delegate, 337 const ExtensionInstallPrompt::Prompt& prompt) 338 : ExternalInstallMenuAlert(service, extension), 339 delegate_(delegate), 340 prompt_(&prompt) { 341} 342 343ExternalInstallGlobalError::~ExternalInstallGlobalError() { 344 if (delegate_) 345 delegate_->Release(); 346} 347 348void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) { 349 ShowBubbleView(browser); 350} 351 352bool ExternalInstallGlobalError::HasBubbleView() { 353 return true; 354} 355 356gfx::Image ExternalInstallGlobalError::GetBubbleViewIcon() { 357 if (prompt_->icon().IsEmpty()) 358 return GlobalErrorWithStandardBubble::GetBubbleViewIcon(); 359 // Scale icon to a reasonable size. 360 return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage( 361 *prompt_->icon().ToImageSkia(), 362 skia::ImageOperations::RESIZE_BEST, 363 gfx::Size(extension_misc::EXTENSION_ICON_SMALL, 364 extension_misc::EXTENSION_ICON_SMALL))); 365} 366 367base::string16 ExternalInstallGlobalError::GetBubbleViewTitle() { 368 return prompt_->GetDialogTitle(); 369} 370 371std::vector<base::string16> 372ExternalInstallGlobalError::GetBubbleViewMessages() { 373 std::vector<base::string16> messages; 374 messages.push_back(prompt_->GetHeading()); 375 if (prompt_->GetPermissionCount()) { 376 messages.push_back(prompt_->GetPermissionsHeading()); 377 for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) { 378 messages.push_back(l10n_util::GetStringFUTF16( 379 IDS_EXTENSION_PERMISSION_LINE, 380 prompt_->GetPermission(i))); 381 } 382 } 383 // TODO(yoz): OAuth issue advice? 384 return messages; 385} 386 387base::string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() { 388 return prompt_->GetAcceptButtonLabel(); 389} 390 391base::string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() { 392 return prompt_->GetAbortButtonLabel(); 393} 394 395void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) { 396} 397 398void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed( 399 Browser* browser) { 400 ExternalInstallDialogDelegate* delegate = delegate_; 401 delegate_ = NULL; 402 delegate->InstallUIProceed(); 403} 404 405void ExternalInstallGlobalError::BubbleViewCancelButtonPressed( 406 Browser* browser) { 407 ExternalInstallDialogDelegate* delegate = delegate_; 408 delegate_ = NULL; 409 delegate->InstallUIAbort(true); 410} 411 412// Public interface --------------------------------------------------------- 413 414void AddExternalInstallError(ExtensionService* service, 415 const Extension* extension, 416 bool is_new_profile) { 417 GlobalErrorService* error_service = 418 GlobalErrorServiceFactory::GetForProfile(service->profile()); 419 if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId)) 420 return; 421 422 if (UseBubbleInstall(extension, is_new_profile)) { 423 Browser* browser = NULL; 424#if !defined(OS_ANDROID) 425 browser = chrome::FindTabbedBrowser(service->profile(), 426 true, 427 chrome::GetActiveDesktop()); 428#endif 429 new ExternalInstallDialogDelegate(browser, service, extension, true); 430 } else { 431 error_service->AddGlobalError( 432 new ExternalInstallMenuAlert(service, extension)); 433 } 434} 435 436void RemoveExternalInstallError(ExtensionService* service) { 437 GlobalErrorService* error_service = 438 GlobalErrorServiceFactory::GetForProfile(service->profile()); 439 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID( 440 kMenuCommandId); 441 if (error) { 442 error_service->RemoveGlobalError(error); 443 delete error; 444 } 445} 446 447bool HasExternalInstallError(ExtensionService* service) { 448 GlobalErrorService* error_service = 449 GlobalErrorServiceFactory::GetForProfile(service->profile()); 450 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID( 451 kMenuCommandId); 452 return !!error; 453} 454 455bool HasExternalInstallBubble(ExtensionService* service) { 456 GlobalErrorService* error_service = 457 GlobalErrorServiceFactory::GetForProfile(service->profile()); 458 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID( 459 kMenuCommandId); 460 return error && error->HasBubbleView(); 461} 462 463} // namespace extensions 464