NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / extensions / external_install_ui.cc
bloba071228dc0f6ab82934798a5d4029e81511fb4ed
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"
7 #include <string>
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/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 {
48 namespace {
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;
55 } // namespace
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> {
66 public:
67 ExternalInstallDialogDelegate(Browser* browser,
68 ExtensionService* service,
69 const Extension* extension,
70 bool use_global_error);
72 Browser* browser() { return browser_; }
74 private:
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.
92 void ShowInstallUI();
94 // The UI for showing the install dialog when enabling.
95 scoped_ptr<ExtensionInstallPrompt> install_ui_;
96 scoped_ptr<ExtensionInstallPrompt::Prompt> prompt_;
98 Browser* browser_;
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 {
111 public:
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;
136 protected:
137 ExtensionService* service_;
138 const Extension* extension_;
139 content::NotificationRegistrar registrar_;
141 private:
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 {
147 public:
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;
165 protected:
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
168 // manually).
169 ExternalInstallDialogDelegate* delegate_;
170 const ExtensionInstallPrompt::Prompt* prompt_;
172 private:
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) {
182 if (!service.get())
183 return;
184 const Extension* extension = service->GetInstalledExtension(extension_id);
185 if (!extension)
186 return;
187 GlobalErrorService* error_service =
188 GlobalErrorServiceFactory::GetForProfile(service->profile());
189 if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
190 return;
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,
204 Browser* browser,
205 const Extension* extension) {
206 // This object manages its own lifetime.
207 new ExternalInstallDialogDelegate(browser, service, extension, false);
210 // ExternalInstallDialogDelegate --------------------------------------------
212 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
213 Browser* browser,
214 ExtensionService* service,
215 const Extension* extension,
216 bool use_global_error)
217 : browser_(browser),
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.
228 if (!browser) {
229 ShowInstallUI();
230 return;
233 webstore_data_fetcher_.reset(new WebstoreDataFetcher(
234 this,
235 browser->profile()->GetRequestContext(),
236 GURL::EmptyGURL(),
237 extension->id()));
238 webstore_data_fetcher_->Start();
241 void ExternalInstallDialogDelegate::OnWebstoreRequestFailure() {
242 ShowInstallUI();
245 void ExternalInstallDialogDelegate::OnWebstoreResponseParseSuccess(
246 scoped_ptr<base::DictionaryValue> webstore_data) {
247 std::string localized_user_count;
248 double average_rating;
249 int rating_count;
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.
255 ShowInstallUI();
256 return;
259 bool show_user_count = true;
260 webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
262 prompt_->SetWebstoreData(localized_user_count,
263 show_user_count,
264 average_rating,
265 rating_count);
267 ShowInstallUI();
270 void ExternalInstallDialogDelegate::OnWebstoreResponseParseFailure(
271 const std::string& error) {
272 ShowInstallUI();
275 void ExternalInstallDialogDelegate::ShowInstallUI() {
276 const Extension* extension = NULL;
277 if (!service_weak_.get() ||
278 !(extension = service_weak_->GetInstalledExtension(extension_id_))) {
279 return;
281 install_ui_.reset(
282 ExtensionInstallUI::CreateInstallPromptWithBrowser(browser_));
284 const ExtensionInstallPrompt::ShowDialogCallback callback =
285 use_global_error_ ?
286 base::Bind(&CreateExternalInstallGlobalError,
287 service_weak_,
288 extension_id_) :
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_))) {
301 return;
303 service_weak_->GrantPermissionsAndEnableExtension(extension);
304 Release();
307 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated) {
308 const Extension* extension = NULL;
309 if (!service_weak_.get() ||
310 !(extension = service_weak_->GetInstalledExtension(extension_id_))) {
311 return;
313 service_weak_->UninstallExtension(extension_id_, false, NULL);
314 Release();
317 // ExternalInstallMenuAlert -------------------------------------------------
319 ExternalInstallMenuAlert::ExternalInstallMenuAlert(
320 ExtensionService* service,
321 const Extension* extension)
322 : service_(service),
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() {
334 return SEVERITY_LOW;
337 bool ExternalInstallMenuAlert::HasMenuItem() {
338 return true;
341 int ExternalInstallMenuAlert::MenuItemCommandID() {
342 return kMenuCommandId;
345 base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
346 int id = -1;
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;
351 else
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() {
361 return false;
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) {
380 NOTREACHED();
383 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
384 Browser* browser) {
385 NOTREACHED();
388 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
389 Browser* browser) {
390 NOTREACHED();
393 void ExternalInstallMenuAlert::Observe(
394 int type,
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_)
402 return;
403 GlobalErrorService* error_service =
404 GlobalErrorServiceFactory::GetForProfile(service_->profile());
405 error_service->RemoveGlobalError(this);
406 service_->AcknowledgeExternalExtension(extension_->id());
407 delete this;
410 // ExternalInstallGlobalError -----------------------------------------------
412 ExternalInstallGlobalError::ExternalInstallGlobalError(
413 ExtensionService* service,
414 const Extension* extension,
415 ExternalInstallDialogDelegate* delegate,
416 const ExtensionInstallPrompt::Prompt& prompt)
417 : ExternalInstallMenuAlert(service, extension),
418 delegate_(delegate),
419 prompt_(&prompt) {
422 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
423 if (delegate_)
424 delegate_->Release();
427 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) {
428 ShowBubbleView(browser);
431 bool ExternalInstallGlobalError::HasBubbleView() {
432 return true;
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?
463 return messages;
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(
478 Browser* browser) {
479 ExternalInstallDialogDelegate* delegate = delegate_;
480 delegate_ = NULL;
481 delegate->InstallUIProceed();
484 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
485 Browser* browser) {
486 ExternalInstallDialogDelegate* delegate = delegate_;
487 delegate_ = NULL;
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))
499 return;
501 if (UseBubbleInstall(extension, is_new_profile)) {
502 Browser* browser = NULL;
503 #if !defined(OS_ANDROID)
504 browser = chrome::FindTabbedBrowser(service->profile(),
505 true,
506 chrome::GetActiveDesktop());
507 #endif
508 new ExternalInstallDialogDelegate(browser, service, extension, true);
509 } else {
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(
519 kMenuCommandId);
520 if (error) {
521 error_service->RemoveGlobalError(error);
522 delete error;
526 bool HasExternalInstallError(ExtensionService* service) {
527 GlobalErrorService* error_service =
528 GlobalErrorServiceFactory::GetForProfile(service->profile());
529 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
530 kMenuCommandId);
531 return !!error;
534 bool HasExternalInstallBubble(ExtensionService* service) {
535 GlobalErrorService* error_service =
536 GlobalErrorServiceFactory::GetForProfile(service->profile());
537 GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
538 kMenuCommandId);
539 return error && error->HasBubbleView();
542 } // namespace extensions