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.
5 #include "chrome/browser/extensions/external_install_ui.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"
44 namespace extensions
{
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
;
55 static const int kMenuCommandId
= IDC_EXTERNAL_EXTENSION_ALERT
;
57 class ExternalInstallGlobalError
;
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
> {
66 ExternalInstallDialogDelegate(Browser
* browser
,
67 ExtensionService
* service
,
68 const Extension
* extension
,
69 bool use_global_error
);
71 Browser
* browser() { return browser_
; }
74 friend class base::RefCountedThreadSafe
<ExternalInstallDialogDelegate
>;
75 friend class ExternalInstallGlobalError
;
77 virtual ~ExternalInstallDialogDelegate();
79 // ExtensionInstallPrompt::Delegate:
80 virtual void InstallUIProceed() OVERRIDE
;
81 virtual void InstallUIAbort(bool user_initiated
) OVERRIDE
;
83 // The UI for showing the install dialog when enabling.
84 scoped_ptr
<ExtensionInstallPrompt
> install_ui_
;
87 base::WeakPtr
<ExtensionService
> service_weak_
;
88 const std::string extension_id_
;
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
{
96 ExternalInstallMenuAlert(ExtensionService
* service
,
97 const Extension
* extension
);
98 virtual ~ExternalInstallMenuAlert();
100 const Extension
* extension() const { return extension_
; }
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
;
117 // content::NotificationObserver implementation.
118 virtual void Observe(int type
,
119 const content::NotificationSource
& source
,
120 const content::NotificationDetails
& details
) OVERRIDE
;
123 ExtensionService
* service_
;
124 const Extension
* extension_
;
125 content::NotificationRegistrar registrar_
;
128 // Shows a menu item and a global error bubble, replacing the install dialog.
129 class ExternalInstallGlobalError
: public ExternalInstallMenuAlert
{
131 ExternalInstallGlobalError(ExtensionService
* service
,
132 const Extension
* extension
,
133 ExternalInstallDialogDelegate
* delegate
,
134 const ExtensionInstallPrompt::Prompt
& prompt
);
135 virtual ~ExternalInstallGlobalError();
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
;
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
152 ExternalInstallDialogDelegate
* delegate_
;
153 const ExtensionInstallPrompt::Prompt
* prompt_
;
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
) {
164 const Extension
* extension
= service
->GetInstalledExtension(extension_id
);
167 GlobalErrorService
* error_service
=
168 GlobalErrorServiceFactory::GetForProfile(service
->profile());
169 if (error_service
->GetGlobalErrorByMenuItemCommandID(kMenuCommandId
))
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());
182 static void ShowExternalInstallDialog(
183 ExtensionService
* service
,
185 const Extension
* extension
) {
186 // This object manages its own lifetime.
187 new ExternalInstallDialogDelegate(browser
, service
, extension
, false);
190 // ExternalInstallDialogDelegate --------------------------------------------
192 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
194 ExtensionService
* service
,
195 const Extension
* extension
,
196 bool use_global_error
)
198 service_weak_(service
->AsWeakPtr()),
199 extension_id_(extension
->id()) {
200 AddRef(); // Balanced in Proceed or Abort.
203 ExtensionInstallUI::CreateInstallPromptWithBrowser(browser
));
205 const ExtensionInstallPrompt::ShowDialogCallback callback
=
207 base::Bind(&CreateExternalInstallGlobalError
,
208 service_weak_
, extension_id_
) :
209 ExtensionInstallPrompt::GetDefaultShowDialogCallback();
210 install_ui_
->ConfirmExternalInstall(this, extension
, callback
);
213 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
216 void ExternalInstallDialogDelegate::InstallUIProceed() {
217 if (!service_weak_
.get())
219 const Extension
* extension
=
220 service_weak_
->GetInstalledExtension(extension_id_
);
223 service_weak_
->GrantPermissionsAndEnableExtension(extension
);
227 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated
) {
228 if (!service_weak_
.get())
230 const Extension
* extension
=
231 service_weak_
->GetInstalledExtension(extension_id_
);
234 service_weak_
->UninstallExtension(extension_id_
, false, NULL
);
238 // ExternalInstallMenuAlert -------------------------------------------------
240 ExternalInstallMenuAlert::ExternalInstallMenuAlert(
241 ExtensionService
* service
,
242 const Extension
* extension
)
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()));
251 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
254 GlobalError::Severity
ExternalInstallMenuAlert::GetSeverity() {
258 bool ExternalInstallMenuAlert::HasMenuItem() {
262 int ExternalInstallMenuAlert::MenuItemCommandID() {
263 return kMenuCommandId
;
266 base::string16
ExternalInstallMenuAlert::MenuItemLabel() {
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
;
273 id
= IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION
;
274 return l10n_util::GetStringFUTF16(id
, base::UTF8ToUTF16(extension_
->name()));
277 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser
* browser
) {
278 ShowExternalInstallDialog(service_
, browser
, extension_
);
281 bool ExternalInstallMenuAlert::HasBubbleView() {
284 base::string16
ExternalInstallMenuAlert::GetBubbleViewTitle() {
285 return base::string16();
288 std::vector
<base::string16
> ExternalInstallMenuAlert::GetBubbleViewMessages() {
289 return std::vector
<base::string16
>();
292 base::string16
ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
293 return base::string16();
296 base::string16
ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
297 return base::string16();
300 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser
* browser
) {
304 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
309 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
314 void ExternalInstallMenuAlert::Observe(
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_
)
324 GlobalErrorService
* error_service
=
325 GlobalErrorServiceFactory::GetForProfile(service_
->profile());
326 error_service
->RemoveGlobalError(this);
327 service_
->AcknowledgeExternalExtension(extension_
->id());
331 // ExternalInstallGlobalError -----------------------------------------------
333 ExternalInstallGlobalError::ExternalInstallGlobalError(
334 ExtensionService
* service
,
335 const Extension
* extension
,
336 ExternalInstallDialogDelegate
* delegate
,
337 const ExtensionInstallPrompt::Prompt
& prompt
)
338 : ExternalInstallMenuAlert(service
, extension
),
343 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
345 delegate_
->Release();
348 void ExternalInstallGlobalError::ExecuteMenuItem(Browser
* browser
) {
349 ShowBubbleView(browser
);
352 bool ExternalInstallGlobalError::HasBubbleView() {
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
)));
367 base::string16
ExternalInstallGlobalError::GetBubbleViewTitle() {
368 return prompt_
->GetDialogTitle();
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
)));
383 // TODO(yoz): OAuth issue advice?
387 base::string16
ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
388 return prompt_
->GetAcceptButtonLabel();
391 base::string16
ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
392 return prompt_
->GetAbortButtonLabel();
395 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser
* browser
) {
398 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
400 ExternalInstallDialogDelegate
* delegate
= delegate_
;
402 delegate
->InstallUIProceed();
405 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
407 ExternalInstallDialogDelegate
* delegate
= delegate_
;
409 delegate
->InstallUIAbort(true);
412 // Public interface ---------------------------------------------------------
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
))
422 if (UseBubbleInstall(extension
, is_new_profile
)) {
423 Browser
* browser
= NULL
;
424 #if !defined(OS_ANDROID)
425 browser
= chrome::FindTabbedBrowser(service
->profile(),
427 chrome::GetActiveDesktop());
429 new ExternalInstallDialogDelegate(browser
, service
, extension
, true);
431 error_service
->AddGlobalError(
432 new ExternalInstallMenuAlert(service
, extension
));
436 void RemoveExternalInstallError(ExtensionService
* service
) {
437 GlobalErrorService
* error_service
=
438 GlobalErrorServiceFactory::GetForProfile(service
->profile());
439 GlobalError
* error
= error_service
->GetGlobalErrorByMenuItemCommandID(
442 error_service
->RemoveGlobalError(error
);
447 bool HasExternalInstallError(ExtensionService
* service
) {
448 GlobalErrorService
* error_service
=
449 GlobalErrorServiceFactory::GetForProfile(service
->profile());
450 GlobalError
* error
= error_service
->GetGlobalErrorByMenuItemCommandID(
455 bool HasExternalInstallBubble(ExtensionService
* service
) {
456 GlobalErrorService
* error_service
=
457 GlobalErrorServiceFactory::GetForProfile(service
->profile());
458 GlobalError
* error
= error_service
->GetGlobalErrorByMenuItemCommandID(
460 return error
&& error
->HasBubbleView();
463 } // namespace extensions