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 44 namespace extensions { 45 46 namespace { 47 48 // Whether the external extension can use the streamlined bubble install flow. 49 bool UseBubbleInstall(const Extension* extension, bool is_new_profile) { 50 return ManifestURL::UpdatesFromGallery(extension) && !is_new_profile; 51 } 52 53 } // namespace 54 55 static const int kMenuCommandId = IDC_EXTERNAL_EXTENSION_ALERT; 56 57 class 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. 62 class 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. 93 class 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. 129 class 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 156 static 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 182 static 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 192 ExternalInstallDialogDelegate::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 213 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() { 214 } 215 216 void 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 227 void 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 240 ExternalInstallMenuAlert::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 251 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() { 252 } 253 254 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() { 255 return SEVERITY_LOW; 256 } 257 258 bool ExternalInstallMenuAlert::HasMenuItem() { 259 return true; 260 } 261 262 int ExternalInstallMenuAlert::MenuItemCommandID() { 263 return kMenuCommandId; 264 } 265 266 base::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 277 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) { 278 ShowExternalInstallDialog(service_, browser, extension_); 279 } 280 281 bool ExternalInstallMenuAlert::HasBubbleView() { 282 return false; 283 } 284 base::string16 ExternalInstallMenuAlert::GetBubbleViewTitle() { 285 return base::string16(); 286 } 287 288 std::vector<base::string16> ExternalInstallMenuAlert::GetBubbleViewMessages() { 289 return std::vector<base::string16>(); 290 } 291 292 base::string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() { 293 return base::string16(); 294 } 295 296 base::string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() { 297 return base::string16(); 298 } 299 300 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) { 301 NOTREACHED(); 302 } 303 304 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed( 305 Browser* browser) { 306 NOTREACHED(); 307 } 308 309 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed( 310 Browser* browser) { 311 NOTREACHED(); 312 } 313 314 void 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 333 ExternalInstallGlobalError::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 343 ExternalInstallGlobalError::~ExternalInstallGlobalError() { 344 if (delegate_) 345 delegate_->Release(); 346 } 347 348 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) { 349 ShowBubbleView(browser); 350 } 351 352 bool ExternalInstallGlobalError::HasBubbleView() { 353 return true; 354 } 355 356 gfx::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 367 base::string16 ExternalInstallGlobalError::GetBubbleViewTitle() { 368 return prompt_->GetDialogTitle(); 369 } 370 371 std::vector<base::string16> 372 ExternalInstallGlobalError::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 387 base::string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() { 388 return prompt_->GetAcceptButtonLabel(); 389 } 390 391 base::string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() { 392 return prompt_->GetAbortButtonLabel(); 393 } 394 395 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) { 396 } 397 398 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed( 399 Browser* browser) { 400 ExternalInstallDialogDelegate* delegate = delegate_; 401 delegate_ = NULL; 402 delegate->InstallUIProceed(); 403 } 404 405 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed( 406 Browser* browser) { 407 ExternalInstallDialogDelegate* delegate = delegate_; 408 delegate_ = NULL; 409 delegate->InstallUIAbort(true); 410 } 411 412 // Public interface --------------------------------------------------------- 413 414 void 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 436 void 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 447 bool 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 455 bool 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