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