Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / external_install_ui.cc
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/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"
45
46 namespace extensions {
47
48 namespace {
49
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;
53 }
54
55 }  // namespace
56
57 static const int kMenuCommandId = IDC_EXTERNAL_EXTENSION_ALERT;
58
59 class ExternalInstallGlobalError;
60
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);
71
72   Browser* browser() { return browser_; }
73
74  private:
75   friend class base::RefCountedThreadSafe<ExternalInstallDialogDelegate>;
76   friend class ExternalInstallGlobalError;
77
78   virtual ~ExternalInstallDialogDelegate();
79
80   // ExtensionInstallPrompt::Delegate:
81   virtual void InstallUIProceed() OVERRIDE;
82   virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
83
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;
90
91   // Show the install dialog to the user.
92   void ShowInstallUI();
93
94   // The UI for showing the install dialog when enabling.
95   scoped_ptr<ExtensionInstallPrompt> install_ui_;
96   scoped_ptr<ExtensionInstallPrompt::Prompt> prompt_;
97
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_;
103
104   DISALLOW_COPY_AND_ASSIGN(ExternalInstallDialogDelegate);
105 };
106
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();
115
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;
130
131   // content::NotificationObserver implementation.
132   virtual void Observe(int type,
133                        const content::NotificationSource& source,
134                        const content::NotificationDetails& details) OVERRIDE;
135
136  protected:
137   ExtensionService* service_;
138   const Extension* extension_;
139   content::NotificationRegistrar registrar_;
140
141  private:
142   DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert);
143 };
144
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();
153
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;
164
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_;
171
172  private:
173   DISALLOW_COPY_AND_ASSIGN(ExternalInstallGlobalError);
174 };
175
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;
191
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());
200 }
201
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);
208 }
209
210 // ExternalInstallDialogDelegate --------------------------------------------
211
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.
222
223   prompt_.reset(new ExtensionInstallPrompt::Prompt(
224       ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT));
225
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;
231   }
232
233   webstore_data_fetcher_.reset(new WebstoreDataFetcher(
234       this,
235       browser->profile()->GetRequestContext(),
236       GURL::EmptyGURL(),
237       extension->id()));
238   webstore_data_fetcher_->Start();
239 }
240
241 void ExternalInstallDialogDelegate::OnWebstoreRequestFailure() {
242   ShowInstallUI();
243 }
244
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;
257   }
258
259   bool show_user_count = true;
260   webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
261
262   prompt_->SetWebstoreData(localized_user_count,
263                            show_user_count,
264                            average_rating,
265                            rating_count);
266
267   ShowInstallUI();
268 }
269
270 void ExternalInstallDialogDelegate::OnWebstoreResponseParseFailure(
271     const std::string& error) {
272   ShowInstallUI();
273 }
274
275 void ExternalInstallDialogDelegate::ShowInstallUI() {
276   const Extension* extension = NULL;
277   if (!service_weak_.get() ||
278       !(extension = service_weak_->GetInstalledExtension(extension_id_))) {
279     return;
280   }
281   install_ui_.reset(
282       ExtensionInstallUI::CreateInstallPromptWithBrowser(browser_));
283
284   const ExtensionInstallPrompt::ShowDialogCallback callback =
285       use_global_error_ ?
286           base::Bind(&CreateExternalInstallGlobalError,
287                      service_weak_,
288                      extension_id_) :
289           ExtensionInstallPrompt::GetDefaultShowDialogCallback();
290
291   install_ui_->ConfirmExternalInstall(this, extension, callback, *prompt_);
292 }
293
294 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
295 }
296
297 void ExternalInstallDialogDelegate::InstallUIProceed() {
298   const Extension* extension = NULL;
299   if (!service_weak_.get() ||
300       !(extension = service_weak_->GetInstalledExtension(extension_id_))) {
301     return;
302   }
303   service_weak_->GrantPermissionsAndEnableExtension(extension);
304   Release();
305 }
306
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;
312   }
313   service_weak_->UninstallExtension(extension_id_, false, NULL);
314   Release();
315 }
316
317 // ExternalInstallMenuAlert -------------------------------------------------
318
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()));
328 }
329
330 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
331 }
332
333 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
334   return SEVERITY_LOW;
335 }
336
337 bool ExternalInstallMenuAlert::HasMenuItem() {
338   return true;
339 }
340
341 int ExternalInstallMenuAlert::MenuItemCommandID() {
342   return kMenuCommandId;
343 }
344
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()));
354 }
355
356 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
357   ShowExternalInstallDialog(service_, browser, extension_);
358 }
359
360 bool ExternalInstallMenuAlert::HasBubbleView() {
361   return false;
362 }
363 base::string16 ExternalInstallMenuAlert::GetBubbleViewTitle() {
364   return base::string16();
365 }
366
367 std::vector<base::string16> ExternalInstallMenuAlert::GetBubbleViewMessages() {
368   return std::vector<base::string16>();
369 }
370
371 base::string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
372   return base::string16();
373 }
374
375 base::string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
376   return base::string16();
377 }
378
379 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) {
380   NOTREACHED();
381 }
382
383 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
384     Browser* browser) {
385   NOTREACHED();
386 }
387
388 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
389     Browser* browser) {
390   NOTREACHED();
391 }
392
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;
408 }
409
410 // ExternalInstallGlobalError -----------------------------------------------
411
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) {
420 }
421
422 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
423   if (delegate_)
424     delegate_->Release();
425 }
426
427 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) {
428   ShowBubbleView(browser);
429 }
430
431 bool ExternalInstallGlobalError::HasBubbleView() {
432   return true;
433 }
434
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)));
444 }
445
446 base::string16 ExternalInstallGlobalError::GetBubbleViewTitle() {
447   return prompt_->GetDialogTitle();
448 }
449
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)));
460     }
461   }
462   // TODO(yoz): OAuth issue advice?
463   return messages;
464 }
465
466 base::string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
467   return prompt_->GetAcceptButtonLabel();
468 }
469
470 base::string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
471   return prompt_->GetAbortButtonLabel();
472 }
473
474 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) {
475 }
476
477 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
478     Browser* browser) {
479   ExternalInstallDialogDelegate* delegate = delegate_;
480   delegate_ = NULL;
481   delegate->InstallUIProceed();
482 }
483
484 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
485     Browser* browser) {
486   ExternalInstallDialogDelegate* delegate = delegate_;
487   delegate_ = NULL;
488   delegate->InstallUIAbort(true);
489 }
490
491 // Public interface ---------------------------------------------------------
492
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;
500
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));
512   }
513 }
514
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;
523   }
524 }
525
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;
532 }
533
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();
540 }
541
542 }  // namespace extensions