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/extensions/webstore_data_fetcher.h"
23 #include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/browser.h"
26 #include "chrome/browser/ui/browser_finder.h"
27 #include "chrome/browser/ui/global_error/global_error.h"
28 #include "chrome/browser/ui/global_error/global_error_service.h"
29 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
30 #include "chrome/browser/ui/host_desktop.h"
31 #include "chrome/common/extensions/extension_constants.h"
32 #include "chrome/common/extensions/manifest_url_handler.h"
33 #include "content/public/browser/notification_details.h"
34 #include "content/public/browser/notification_observer.h"
35 #include "content/public/browser/notification_registrar.h"
36 #include "content/public/browser/notification_source.h"
37 #include "extensions/common/extension.h"
38 #include "grit/chromium_strings.h"
39 #include "grit/generated_resources.h"
40 #include "grit/theme_resources.h"
41 #include "ui/base/l10n/l10n_util.h"
42 #include "ui/gfx/image/image.h"
43 #include "ui/gfx/image/image_skia_operations.h"
44 #include "ui/gfx/size.h"
46 namespace extensions
{
50 // Whether the external extension can use the streamlined bubble install flow.
51 bool UseBubbleInstall(const Extension
* extension
, bool is_new_profile
) {
52 return ManifestURL::UpdatesFromGallery(extension
) && !is_new_profile
;
57 static const int kMenuCommandId
= IDC_EXTERNAL_EXTENSION_ALERT
;
59 class ExternalInstallGlobalError
;
61 // This class is refcounted to stay alive while we try and pull webstore data.
62 class ExternalInstallDialogDelegate
63 : public ExtensionInstallPrompt::Delegate
,
64 public WebstoreDataFetcherDelegate
,
65 public base::RefCountedThreadSafe
<ExternalInstallDialogDelegate
> {
67 ExternalInstallDialogDelegate(Browser
* browser
,
68 ExtensionService
* service
,
69 const Extension
* extension
,
70 bool use_global_error
);
72 Browser
* browser() { return browser_
; }
75 friend class base::RefCountedThreadSafe
<ExternalInstallDialogDelegate
>;
76 friend class ExternalInstallGlobalError
;
78 virtual ~ExternalInstallDialogDelegate();
80 // ExtensionInstallPrompt::Delegate:
81 virtual void InstallUIProceed() OVERRIDE
;
82 virtual void InstallUIAbort(bool user_initiated
) OVERRIDE
;
84 // WebstoreDataFetcherDelegate:
85 virtual void OnWebstoreRequestFailure() OVERRIDE
;
86 virtual void OnWebstoreResponseParseSuccess(
87 scoped_ptr
<base::DictionaryValue
> webstore_data
) OVERRIDE
;
88 virtual void OnWebstoreResponseParseFailure(
89 const std::string
& error
) OVERRIDE
;
91 // Show the install dialog to the user.
94 // The UI for showing the install dialog when enabling.
95 scoped_ptr
<ExtensionInstallPrompt
> install_ui_
;
96 scoped_ptr
<ExtensionInstallPrompt::Prompt
> prompt_
;
99 base::WeakPtr
<ExtensionService
> service_weak_
;
100 scoped_ptr
<WebstoreDataFetcher
> webstore_data_fetcher_
;
101 std::string extension_id_
;
102 bool use_global_error_
;
104 DISALLOW_COPY_AND_ASSIGN(ExternalInstallDialogDelegate
);
107 // Only shows a menu item, no bubble. Clicking the menu item shows
108 // an external install dialog.
109 class ExternalInstallMenuAlert
: public GlobalErrorWithStandardBubble
,
110 public content::NotificationObserver
{
112 ExternalInstallMenuAlert(ExtensionService
* service
,
113 const Extension
* extension
);
114 virtual ~ExternalInstallMenuAlert();
116 // GlobalError implementation.
117 virtual Severity
GetSeverity() OVERRIDE
;
118 virtual bool HasMenuItem() OVERRIDE
;
119 virtual int MenuItemCommandID() OVERRIDE
;
120 virtual base::string16
MenuItemLabel() OVERRIDE
;
121 virtual void ExecuteMenuItem(Browser
* browser
) OVERRIDE
;
122 virtual bool HasBubbleView() OVERRIDE
;
123 virtual base::string16
GetBubbleViewTitle() OVERRIDE
;
124 virtual std::vector
<base::string16
> GetBubbleViewMessages() OVERRIDE
;
125 virtual base::string16
GetBubbleViewAcceptButtonLabel() OVERRIDE
;
126 virtual base::string16
GetBubbleViewCancelButtonLabel() OVERRIDE
;
127 virtual void OnBubbleViewDidClose(Browser
* browser
) OVERRIDE
;
128 virtual void BubbleViewAcceptButtonPressed(Browser
* browser
) OVERRIDE
;
129 virtual void BubbleViewCancelButtonPressed(Browser
* browser
) OVERRIDE
;
131 // content::NotificationObserver implementation.
132 virtual void Observe(int type
,
133 const content::NotificationSource
& source
,
134 const content::NotificationDetails
& details
) OVERRIDE
;
137 ExtensionService
* service_
;
138 const Extension
* extension_
;
139 content::NotificationRegistrar registrar_
;
142 DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert
);
145 // Shows a menu item and a global error bubble, replacing the install dialog.
146 class ExternalInstallGlobalError
: public ExternalInstallMenuAlert
{
148 ExternalInstallGlobalError(ExtensionService
* service
,
149 const Extension
* extension
,
150 ExternalInstallDialogDelegate
* delegate
,
151 const ExtensionInstallPrompt::Prompt
& prompt
);
152 virtual ~ExternalInstallGlobalError();
154 virtual void ExecuteMenuItem(Browser
* browser
) OVERRIDE
;
155 virtual bool HasBubbleView() OVERRIDE
;
156 virtual gfx::Image
GetBubbleViewIcon() OVERRIDE
;
157 virtual base::string16
GetBubbleViewTitle() OVERRIDE
;
158 virtual std::vector
<base::string16
> GetBubbleViewMessages() OVERRIDE
;
159 virtual base::string16
GetBubbleViewAcceptButtonLabel() OVERRIDE
;
160 virtual base::string16
GetBubbleViewCancelButtonLabel() OVERRIDE
;
161 virtual void OnBubbleViewDidClose(Browser
* browser
) OVERRIDE
;
162 virtual void BubbleViewAcceptButtonPressed(Browser
* browser
) OVERRIDE
;
163 virtual void BubbleViewCancelButtonPressed(Browser
* browser
) OVERRIDE
;
166 // Ref-counted, but needs to be disposed of if we are dismissed without
167 // having been clicked (perhaps because the user enabled the extension
169 ExternalInstallDialogDelegate
* delegate_
;
170 const ExtensionInstallPrompt::Prompt
* prompt_
;
173 DISALLOW_COPY_AND_ASSIGN(ExternalInstallGlobalError
);
176 static void CreateExternalInstallGlobalError(
177 base::WeakPtr
<ExtensionService
> service
,
178 const std::string
& extension_id
,
179 const ExtensionInstallPrompt::ShowParams
& show_params
,
180 ExtensionInstallPrompt::Delegate
* prompt_delegate
,
181 const ExtensionInstallPrompt::Prompt
& prompt
) {
184 const Extension
* extension
= service
->GetInstalledExtension(extension_id
);
187 GlobalErrorService
* error_service
=
188 GlobalErrorServiceFactory::GetForProfile(service
->profile());
189 if (error_service
->GetGlobalErrorByMenuItemCommandID(kMenuCommandId
))
192 ExternalInstallDialogDelegate
* delegate
=
193 static_cast<ExternalInstallDialogDelegate
*>(prompt_delegate
);
194 ExternalInstallGlobalError
* error_bubble
= new ExternalInstallGlobalError(
195 service
.get(), extension
, delegate
, prompt
);
196 error_service
->AddGlobalError(error_bubble
);
197 // Show bubble immediately if possible.
198 if (delegate
->browser())
199 error_bubble
->ShowBubbleView(delegate
->browser());
202 static void ShowExternalInstallDialog(
203 ExtensionService
* service
,
205 const Extension
* extension
) {
206 // This object manages its own lifetime.
207 new ExternalInstallDialogDelegate(browser
, service
, extension
, false);
210 // ExternalInstallDialogDelegate --------------------------------------------
212 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
214 ExtensionService
* service
,
215 const Extension
* extension
,
216 bool use_global_error
)
218 service_weak_(service
->AsWeakPtr()),
219 extension_id_(extension
->id()),
220 use_global_error_(use_global_error
) {
221 AddRef(); // Balanced in Proceed or Abort.
223 prompt_
.reset(new ExtensionInstallPrompt::Prompt(
224 ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT
));
226 // If we don't have a browser, we can't go to the webstore to fetch data.
227 // This should only happen in tests.
233 webstore_data_fetcher_
.reset(new WebstoreDataFetcher(
235 browser
->profile()->GetRequestContext(),
238 webstore_data_fetcher_
->Start();
241 void ExternalInstallDialogDelegate::OnWebstoreRequestFailure() {
245 void ExternalInstallDialogDelegate::OnWebstoreResponseParseSuccess(
246 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
247 std::string localized_user_count
;
248 double average_rating
;
250 if (!webstore_data
->GetString(kUsersKey
, &localized_user_count
) ||
251 !webstore_data
->GetDouble(kAverageRatingKey
, &average_rating
) ||
252 !webstore_data
->GetInteger(kRatingCountKey
, &rating_count
)) {
253 // If we don't get a valid webstore response, short circuit, and continue
254 // to show a prompt without webstore data.
259 bool show_user_count
= true;
260 webstore_data
->GetBoolean(kShowUserCountKey
, &show_user_count
);
262 prompt_
->SetWebstoreData(localized_user_count
,
270 void ExternalInstallDialogDelegate::OnWebstoreResponseParseFailure(
271 const std::string
& error
) {
275 void ExternalInstallDialogDelegate::ShowInstallUI() {
276 const Extension
* extension
= NULL
;
277 if (!service_weak_
.get() ||
278 !(extension
= service_weak_
->GetInstalledExtension(extension_id_
))) {
282 ExtensionInstallUI::CreateInstallPromptWithBrowser(browser_
));
284 const ExtensionInstallPrompt::ShowDialogCallback callback
=
286 base::Bind(&CreateExternalInstallGlobalError
,
289 ExtensionInstallPrompt::GetDefaultShowDialogCallback();
291 install_ui_
->ConfirmExternalInstall(this, extension
, callback
, *prompt_
);
294 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
297 void ExternalInstallDialogDelegate::InstallUIProceed() {
298 const Extension
* extension
= NULL
;
299 if (!service_weak_
.get() ||
300 !(extension
= service_weak_
->GetInstalledExtension(extension_id_
))) {
303 service_weak_
->GrantPermissionsAndEnableExtension(extension
);
307 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated
) {
308 const Extension
* extension
= NULL
;
309 if (!service_weak_
.get() ||
310 !(extension
= service_weak_
->GetInstalledExtension(extension_id_
))) {
313 service_weak_
->UninstallExtension(extension_id_
, false, NULL
);
317 // ExternalInstallMenuAlert -------------------------------------------------
319 ExternalInstallMenuAlert::ExternalInstallMenuAlert(
320 ExtensionService
* service
,
321 const Extension
* extension
)
323 extension_(extension
) {
324 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED
,
325 content::Source
<Profile
>(service
->profile()));
326 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED
,
327 content::Source
<Profile
>(service
->profile()));
330 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
333 GlobalError::Severity
ExternalInstallMenuAlert::GetSeverity() {
337 bool ExternalInstallMenuAlert::HasMenuItem() {
341 int ExternalInstallMenuAlert::MenuItemCommandID() {
342 return kMenuCommandId
;
345 base::string16
ExternalInstallMenuAlert::MenuItemLabel() {
347 if (extension_
->is_app())
348 id
= IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP
;
349 else if (extension_
->is_theme())
350 id
= IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME
;
352 id
= IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION
;
353 return l10n_util::GetStringFUTF16(id
, base::UTF8ToUTF16(extension_
->name()));
356 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser
* browser
) {
357 ShowExternalInstallDialog(service_
, browser
, extension_
);
360 bool ExternalInstallMenuAlert::HasBubbleView() {
363 base::string16
ExternalInstallMenuAlert::GetBubbleViewTitle() {
364 return base::string16();
367 std::vector
<base::string16
> ExternalInstallMenuAlert::GetBubbleViewMessages() {
368 return std::vector
<base::string16
>();
371 base::string16
ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
372 return base::string16();
375 base::string16
ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
376 return base::string16();
379 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser
* browser
) {
383 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
388 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
393 void ExternalInstallMenuAlert::Observe(
395 const content::NotificationSource
& source
,
396 const content::NotificationDetails
& details
) {
397 // The error is invalidated if the extension has been loaded or removed.
398 DCHECK(type
== chrome::NOTIFICATION_EXTENSION_LOADED
||
399 type
== chrome::NOTIFICATION_EXTENSION_REMOVED
);
400 const Extension
* extension
= content::Details
<const Extension
>(details
).ptr();
401 if (extension
!= extension_
)
403 GlobalErrorService
* error_service
=
404 GlobalErrorServiceFactory::GetForProfile(service_
->profile());
405 error_service
->RemoveGlobalError(this);
406 service_
->AcknowledgeExternalExtension(extension_
->id());
410 // ExternalInstallGlobalError -----------------------------------------------
412 ExternalInstallGlobalError::ExternalInstallGlobalError(
413 ExtensionService
* service
,
414 const Extension
* extension
,
415 ExternalInstallDialogDelegate
* delegate
,
416 const ExtensionInstallPrompt::Prompt
& prompt
)
417 : ExternalInstallMenuAlert(service
, extension
),
422 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
424 delegate_
->Release();
427 void ExternalInstallGlobalError::ExecuteMenuItem(Browser
* browser
) {
428 ShowBubbleView(browser
);
431 bool ExternalInstallGlobalError::HasBubbleView() {
435 gfx::Image
ExternalInstallGlobalError::GetBubbleViewIcon() {
436 if (prompt_
->icon().IsEmpty())
437 return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
438 // Scale icon to a reasonable size.
439 return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
440 *prompt_
->icon().ToImageSkia(),
441 skia::ImageOperations::RESIZE_BEST
,
442 gfx::Size(extension_misc::EXTENSION_ICON_SMALL
,
443 extension_misc::EXTENSION_ICON_SMALL
)));
446 base::string16
ExternalInstallGlobalError::GetBubbleViewTitle() {
447 return prompt_
->GetDialogTitle();
450 std::vector
<base::string16
>
451 ExternalInstallGlobalError::GetBubbleViewMessages() {
452 std::vector
<base::string16
> messages
;
453 messages
.push_back(prompt_
->GetHeading());
454 if (prompt_
->GetPermissionCount()) {
455 messages
.push_back(prompt_
->GetPermissionsHeading());
456 for (size_t i
= 0; i
< prompt_
->GetPermissionCount(); ++i
) {
457 messages
.push_back(l10n_util::GetStringFUTF16(
458 IDS_EXTENSION_PERMISSION_LINE
,
459 prompt_
->GetPermission(i
)));
462 // TODO(yoz): OAuth issue advice?
466 base::string16
ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
467 return prompt_
->GetAcceptButtonLabel();
470 base::string16
ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
471 return prompt_
->GetAbortButtonLabel();
474 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser
* browser
) {
477 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
479 ExternalInstallDialogDelegate
* delegate
= delegate_
;
481 delegate
->InstallUIProceed();
484 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
486 ExternalInstallDialogDelegate
* delegate
= delegate_
;
488 delegate
->InstallUIAbort(true);
491 // Public interface ---------------------------------------------------------
493 void AddExternalInstallError(ExtensionService
* service
,
494 const Extension
* extension
,
495 bool is_new_profile
) {
496 GlobalErrorService
* error_service
=
497 GlobalErrorServiceFactory::GetForProfile(service
->profile());
498 if (error_service
->GetGlobalErrorByMenuItemCommandID(kMenuCommandId
))
501 if (UseBubbleInstall(extension
, is_new_profile
)) {
502 Browser
* browser
= NULL
;
503 #if !defined(OS_ANDROID)
504 browser
= chrome::FindTabbedBrowser(service
->profile(),
506 chrome::GetActiveDesktop());
508 new ExternalInstallDialogDelegate(browser
, service
, extension
, true);
510 error_service
->AddGlobalError(
511 new ExternalInstallMenuAlert(service
, extension
));
515 void RemoveExternalInstallError(ExtensionService
* service
) {
516 GlobalErrorService
* error_service
=
517 GlobalErrorServiceFactory::GetForProfile(service
->profile());
518 GlobalError
* error
= error_service
->GetGlobalErrorByMenuItemCommandID(
521 error_service
->RemoveGlobalError(error
);
526 bool HasExternalInstallError(ExtensionService
* service
) {
527 GlobalErrorService
* error_service
=
528 GlobalErrorServiceFactory::GetForProfile(service
->profile());
529 GlobalError
* error
= error_service
->GetGlobalErrorByMenuItemCommandID(
534 bool HasExternalInstallBubble(ExtensionService
* service
) {
535 GlobalErrorService
* error_service
=
536 GlobalErrorServiceFactory::GetForProfile(service
->profile());
537 GlobalError
* error
= error_service
->GetGlobalErrorByMenuItemCommandID(
539 return error
&& error
->HasBubbleView();
542 } // namespace extensions