Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / external_install_ui.cc
1 // Copyright 2014 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/scoped_observer.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/app/chrome_command_ids.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/extensions/extension_install_prompt.h"
20 #include "chrome/browser/extensions/extension_install_ui.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
23 #include "chrome/browser/extensions/webstore_data_fetcher.h"
24 #include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_finder.h"
28 #include "chrome/browser/ui/global_error/global_error.h"
29 #include "chrome/browser/ui/global_error/global_error_service.h"
30 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
31 #include "chrome/common/extensions/manifest_url_handler.h"
32 #include "content/public/browser/notification_details.h"
33 #include "content/public/browser/notification_observer.h"
34 #include "content/public/browser/notification_registrar.h"
35 #include "content/public/browser/notification_source.h"
36 #include "extensions/browser/extension_registry.h"
37 #include "extensions/browser/extension_registry_observer.h"
38 #include "extensions/common/constants.h"
39 #include "grit/generated_resources.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/gfx/image/image.h"
42 #include "ui/gfx/image/image_skia_operations.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 namespace extensions {
60 class ExtensionRegistry;
61 }
62
63 // This class is refcounted to stay alive while we try and pull webstore data.
64 class ExternalInstallDialogDelegate
65     : public ExtensionInstallPrompt::Delegate,
66       public WebstoreDataFetcherDelegate,
67       public content::NotificationObserver,
68       public base::RefCountedThreadSafe<ExternalInstallDialogDelegate> {
69  public:
70   ExternalInstallDialogDelegate(Browser* browser,
71                                 ExtensionService* service,
72                                 const Extension* extension,
73                                 bool use_global_error);
74
75   Browser* browser() { return browser_; }
76
77  private:
78   friend class base::RefCountedThreadSafe<ExternalInstallDialogDelegate>;
79   friend class ExternalInstallGlobalError;
80
81   virtual ~ExternalInstallDialogDelegate();
82
83   // ExtensionInstallPrompt::Delegate:
84   virtual void InstallUIProceed() OVERRIDE;
85   virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
86
87   // WebstoreDataFetcherDelegate:
88   virtual void OnWebstoreRequestFailure() OVERRIDE;
89   virtual void OnWebstoreResponseParseSuccess(
90       scoped_ptr<base::DictionaryValue> webstore_data) OVERRIDE;
91   virtual void OnWebstoreResponseParseFailure(
92       const std::string& error) OVERRIDE;
93
94   // content::NotificationObserver:
95   virtual void Observe(int type,
96                        const content::NotificationSource& source,
97                        const content::NotificationDetails& details) OVERRIDE;
98
99   // Show the install dialog to the user.
100   void ShowInstallUI();
101
102   // The UI for showing the install dialog when enabling.
103   scoped_ptr<ExtensionInstallPrompt> install_ui_;
104   scoped_refptr<ExtensionInstallPrompt::Prompt> prompt_;
105
106   Browser* browser_;
107   base::WeakPtr<ExtensionService> service_weak_;
108   scoped_ptr<WebstoreDataFetcher> webstore_data_fetcher_;
109   content::NotificationRegistrar registrar_;
110   std::string extension_id_;
111   bool use_global_error_;
112
113   DISALLOW_COPY_AND_ASSIGN(ExternalInstallDialogDelegate);
114 };
115
116 // Only shows a menu item, no bubble. Clicking the menu item shows
117 // an external install dialog.
118 class ExternalInstallMenuAlert : public GlobalErrorWithStandardBubble,
119                                  public content::NotificationObserver,
120                                  public ExtensionRegistryObserver {
121  public:
122   ExternalInstallMenuAlert(ExtensionService* service,
123                            const Extension* extension);
124   virtual ~ExternalInstallMenuAlert();
125
126   // GlobalError implementation.
127   virtual Severity GetSeverity() OVERRIDE;
128   virtual bool HasMenuItem() OVERRIDE;
129   virtual int MenuItemCommandID() OVERRIDE;
130   virtual base::string16 MenuItemLabel() OVERRIDE;
131   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
132   virtual bool HasBubbleView() OVERRIDE;
133   virtual base::string16 GetBubbleViewTitle() OVERRIDE;
134   virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
135   virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
136   virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
137   virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
138   virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
139   virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
140
141  protected:
142   ExtensionService* service_;
143   const Extension* extension_;
144
145  private:
146   // Delete this instance after cleaning jobs.
147   void Clean();
148
149   // content::NotificationObserver implementation.
150   virtual void Observe(int type,
151                        const content::NotificationSource& source,
152                        const content::NotificationDetails& details) OVERRIDE;
153
154   // ExtensionRegistryObserver implementation.
155   virtual void OnExtensionLoaded(content::BrowserContext* browser_context,
156                                  const Extension* extension) OVERRIDE;
157
158   content::NotificationRegistrar registrar_;
159
160   // Listen to extension load notifications.
161   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
162       extension_registry_observer_;
163
164   DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert);
165 };
166
167 // Shows a menu item and a global error bubble, replacing the install dialog.
168 class ExternalInstallGlobalError : public ExternalInstallMenuAlert {
169  public:
170   ExternalInstallGlobalError(
171       ExtensionService* service,
172       const Extension* extension,
173       ExternalInstallDialogDelegate* delegate,
174       scoped_refptr<ExtensionInstallPrompt::Prompt> prompt);
175   virtual ~ExternalInstallGlobalError();
176
177   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
178   virtual bool HasBubbleView() OVERRIDE;
179   virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
180   virtual base::string16 GetBubbleViewTitle() OVERRIDE;
181   virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
182   virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
183   virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
184   virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
185   virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
186   virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
187
188  protected:
189   // Ref-counted, but needs to be disposed of if we are dismissed without
190   // having been clicked (perhaps because the user enabled the extension
191   // manually).
192   ExternalInstallDialogDelegate* delegate_;
193   scoped_refptr<ExtensionInstallPrompt::Prompt> prompt_;
194
195  private:
196   DISALLOW_COPY_AND_ASSIGN(ExternalInstallGlobalError);
197 };
198
199 static void CreateExternalInstallGlobalError(
200     base::WeakPtr<ExtensionService> service,
201     const std::string& extension_id,
202     const ExtensionInstallPrompt::ShowParams& show_params,
203     ExtensionInstallPrompt::Delegate* prompt_delegate,
204     scoped_refptr<ExtensionInstallPrompt::Prompt> prompt) {
205   if (!service.get())
206     return;
207   const Extension* extension = service->GetInstalledExtension(extension_id);
208   if (!extension)
209     return;
210   GlobalErrorService* error_service =
211       GlobalErrorServiceFactory::GetForProfile(service->profile());
212   if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
213     return;
214
215   ExternalInstallDialogDelegate* delegate =
216       static_cast<ExternalInstallDialogDelegate*>(prompt_delegate);
217   ExternalInstallGlobalError* error_bubble = new ExternalInstallGlobalError(
218       service.get(), extension, delegate, prompt);
219   error_service->AddGlobalError(error_bubble);
220   // Show bubble immediately if possible.
221   if (delegate->browser())
222     error_bubble->ShowBubbleView(delegate->browser());
223 }
224
225 static void ShowExternalInstallDialog(
226     ExtensionService* service,
227     Browser* browser,
228     const Extension* extension) {
229   // This object manages its own lifetime.
230   new ExternalInstallDialogDelegate(browser, service, extension, false);
231 }
232
233 // ExternalInstallDialogDelegate --------------------------------------------
234
235 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
236     Browser* browser,
237     ExtensionService* service,
238     const Extension* extension,
239     bool use_global_error)
240     : browser_(browser),
241       service_weak_(service->AsWeakPtr()),
242       extension_id_(extension->id()),
243       use_global_error_(use_global_error) {
244   AddRef();  // Balanced in Proceed or Abort.
245
246   prompt_ = new ExtensionInstallPrompt::Prompt(
247       ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT);
248
249   // If we don't have a browser, we can't go to the webstore to fetch data.
250   // This should only happen in tests.
251   if (!browser) {
252     ShowInstallUI();
253     return;
254   }
255
256   // Make sure to be notified if the owning profile is destroyed.
257   registrar_.Add(this,
258                  chrome::NOTIFICATION_PROFILE_DESTROYED,
259                  content::Source<Profile>(browser->profile()));
260
261   webstore_data_fetcher_.reset(new WebstoreDataFetcher(
262       this,
263       browser->profile()->GetRequestContext(),
264       GURL::EmptyGURL(),
265       extension->id()));
266   webstore_data_fetcher_->Start();
267 }
268
269 void ExternalInstallDialogDelegate::OnWebstoreRequestFailure() {
270   ShowInstallUI();
271 }
272
273 void ExternalInstallDialogDelegate::OnWebstoreResponseParseSuccess(
274     scoped_ptr<base::DictionaryValue> webstore_data) {
275   std::string localized_user_count;
276   double average_rating;
277   int rating_count;
278   if (!webstore_data->GetString(kUsersKey, &localized_user_count) ||
279       !webstore_data->GetDouble(kAverageRatingKey, &average_rating) ||
280       !webstore_data->GetInteger(kRatingCountKey, &rating_count)) {
281     // If we don't get a valid webstore response, short circuit, and continue
282     // to show a prompt without webstore data.
283     ShowInstallUI();
284     return;
285   }
286
287   bool show_user_count = true;
288   webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
289
290   prompt_->SetWebstoreData(localized_user_count,
291                            show_user_count,
292                            average_rating,
293                            rating_count);
294
295   ShowInstallUI();
296 }
297
298 void ExternalInstallDialogDelegate::OnWebstoreResponseParseFailure(
299     const std::string& error) {
300   ShowInstallUI();
301 }
302
303 void ExternalInstallDialogDelegate::Observe(
304     int type,
305     const content::NotificationSource& source,
306     const content::NotificationDetails& details) {
307   DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
308   // If the owning profile is destroyed, we need to abort so that we don't leak.
309   InstallUIAbort(false);  // Not user initiated.
310 }
311
312 void ExternalInstallDialogDelegate::ShowInstallUI() {
313   const Extension* extension = NULL;
314   if (!service_weak_.get() ||
315       !(extension = service_weak_->GetInstalledExtension(extension_id_))) {
316     return;
317   }
318   install_ui_.reset(
319       ExtensionInstallUI::CreateInstallPromptWithBrowser(browser_));
320
321   const ExtensionInstallPrompt::ShowDialogCallback callback =
322       use_global_error_ ?
323           base::Bind(&CreateExternalInstallGlobalError,
324                      service_weak_,
325                      extension_id_) :
326           ExtensionInstallPrompt::GetDefaultShowDialogCallback();
327
328   install_ui_->ConfirmExternalInstall(this, extension, callback, prompt_);
329 }
330
331 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
332 }
333
334 void ExternalInstallDialogDelegate::InstallUIProceed() {
335   const Extension* extension = NULL;
336   if (service_weak_.get() &&
337       (extension = service_weak_->GetInstalledExtension(extension_id_))) {
338     service_weak_->GrantPermissionsAndEnableExtension(extension);
339   }
340   Release();
341 }
342
343 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated) {
344   const Extension* extension = NULL;
345
346   // Uninstall the extension if the abort was user initiated (and not, e.g., the
347   // result of the window closing).
348   // Otherwise, the extension will remain installed, but unacknowledged, so it
349   // will be prompted again.
350   if (user_initiated &&
351       service_weak_.get() &&
352       (extension = service_weak_->GetInstalledExtension(extension_id_))) {
353     service_weak_->UninstallExtension(extension_id_, false, NULL);
354   }
355   Release();
356 }
357
358 // ExternalInstallMenuAlert -------------------------------------------------
359
360 ExternalInstallMenuAlert::ExternalInstallMenuAlert(ExtensionService* service,
361                                                    const Extension* extension)
362     : service_(service),
363       extension_(extension),
364       extension_registry_observer_(this) {
365   extension_registry_observer_.Add(ExtensionRegistry::Get(service->profile()));
366   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED,
367                  content::Source<Profile>(service->profile()));
368 }
369
370 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
371 }
372
373 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
374   return SEVERITY_LOW;
375 }
376
377 bool ExternalInstallMenuAlert::HasMenuItem() {
378   return true;
379 }
380
381 int ExternalInstallMenuAlert::MenuItemCommandID() {
382   return kMenuCommandId;
383 }
384
385 base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
386   int id = -1;
387   if (extension_->is_app())
388     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
389   else if (extension_->is_theme())
390     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
391   else
392     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
393   return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension_->name()));
394 }
395
396 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
397   ShowExternalInstallDialog(service_, browser, extension_);
398 }
399
400 bool ExternalInstallMenuAlert::HasBubbleView() {
401   return false;
402 }
403 base::string16 ExternalInstallMenuAlert::GetBubbleViewTitle() {
404   return base::string16();
405 }
406
407 std::vector<base::string16> ExternalInstallMenuAlert::GetBubbleViewMessages() {
408   return std::vector<base::string16>();
409 }
410
411 base::string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
412   return base::string16();
413 }
414
415 base::string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
416   return base::string16();
417 }
418
419 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) {
420   NOTREACHED();
421 }
422
423 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
424     Browser* browser) {
425   NOTREACHED();
426 }
427
428 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
429     Browser* browser) {
430   NOTREACHED();
431 }
432
433 void ExternalInstallMenuAlert::OnExtensionLoaded(
434     content::BrowserContext* browser_context,
435     const Extension* extension) {
436   if (extension == extension_)
437     Clean();
438 }
439
440 void ExternalInstallMenuAlert::Observe(
441     int type,
442     const content::NotificationSource& source,
443     const content::NotificationDetails& details) {
444   // The error is invalidated if the extension has been loaded or removed.
445   DCHECK_EQ(type, chrome::NOTIFICATION_EXTENSION_REMOVED);
446   const Extension* extension = content::Details<const Extension>(details).ptr();
447   if (extension == extension_)
448     Clean();
449 }
450
451 void ExternalInstallMenuAlert::Clean() {
452   GlobalErrorService* error_service =
453       GlobalErrorServiceFactory::GetForProfile(service_->profile());
454   error_service->RemoveGlobalError(this);
455   service_->AcknowledgeExternalExtension(extension_->id());
456   delete this;
457 }
458
459 // ExternalInstallGlobalError -----------------------------------------------
460
461 ExternalInstallGlobalError::ExternalInstallGlobalError(
462     ExtensionService* service,
463     const Extension* extension,
464     ExternalInstallDialogDelegate* delegate,
465     scoped_refptr<ExtensionInstallPrompt::Prompt> prompt)
466     : ExternalInstallMenuAlert(service, extension),
467       delegate_(delegate),
468       prompt_(prompt) {
469 }
470
471 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
472   if (delegate_)
473     delegate_->Release();
474 }
475
476 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) {
477   ShowBubbleView(browser);
478 }
479
480 bool ExternalInstallGlobalError::HasBubbleView() {
481   return true;
482 }
483
484 gfx::Image ExternalInstallGlobalError::GetBubbleViewIcon() {
485   if (prompt_->icon().IsEmpty())
486     return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
487   // Scale icon to a reasonable size.
488   return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
489       *prompt_->icon().ToImageSkia(),
490       skia::ImageOperations::RESIZE_BEST,
491       gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
492                 extension_misc::EXTENSION_ICON_SMALL)));
493 }
494
495 base::string16 ExternalInstallGlobalError::GetBubbleViewTitle() {
496   return prompt_->GetDialogTitle();
497 }
498
499 std::vector<base::string16>
500 ExternalInstallGlobalError::GetBubbleViewMessages() {
501   std::vector<base::string16> messages;
502   messages.push_back(prompt_->GetHeading());
503   if (prompt_->GetPermissionCount()) {
504     messages.push_back(prompt_->GetPermissionsHeading());
505     for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) {
506       messages.push_back(l10n_util::GetStringFUTF16(
507           IDS_EXTENSION_PERMISSION_LINE,
508           prompt_->GetPermission(i)));
509     }
510   }
511   // TODO(yoz): OAuth issue advice?
512   return messages;
513 }
514
515 base::string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
516   return prompt_->GetAcceptButtonLabel();
517 }
518
519 base::string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
520   return prompt_->GetAbortButtonLabel();
521 }
522
523 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) {
524 }
525
526 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
527     Browser* browser) {
528   ExternalInstallDialogDelegate* delegate = delegate_;
529   delegate_ = NULL;
530   delegate->InstallUIProceed();
531 }
532
533 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
534     Browser* browser) {
535   ExternalInstallDialogDelegate* delegate = delegate_;
536   delegate_ = NULL;
537   delegate->InstallUIAbort(true);
538 }
539
540 // Public interface ---------------------------------------------------------
541
542 void AddExternalInstallError(ExtensionService* service,
543                              const Extension* extension,
544                              bool is_new_profile) {
545   GlobalErrorService* error_service =
546       GlobalErrorServiceFactory::GetForProfile(service->profile());
547   if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
548     return;
549
550   if (UseBubbleInstall(extension, is_new_profile)) {
551     Browser* browser = NULL;
552 #if !defined(OS_ANDROID)
553     browser = chrome::FindTabbedBrowser(service->profile(),
554                                         true,
555                                         chrome::GetActiveDesktop());
556 #endif
557     new ExternalInstallDialogDelegate(browser, service, extension, true);
558   } else {
559     error_service->AddGlobalError(
560         new ExternalInstallMenuAlert(service, extension));
561   }
562 }
563
564 void RemoveExternalInstallError(ExtensionService* service) {
565   GlobalErrorService* error_service =
566       GlobalErrorServiceFactory::GetForProfile(service->profile());
567   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
568       kMenuCommandId);
569   if (error) {
570     error_service->RemoveGlobalError(error);
571     delete error;
572   }
573 }
574
575 bool HasExternalInstallError(ExtensionService* service) {
576   GlobalErrorService* error_service =
577       GlobalErrorServiceFactory::GetForProfile(service->profile());
578   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
579       kMenuCommandId);
580   return !!error;
581 }
582
583 bool HasExternalInstallBubble(ExtensionService* service) {
584   GlobalErrorService* error_service =
585       GlobalErrorServiceFactory::GetForProfile(service->profile());
586   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
587       kMenuCommandId);
588   return error && error->HasBubbleView();
589 }
590
591 }  // namespace extensions