- add sources.
[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/profiles/profile.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/browser_finder.h"
25 #include "chrome/browser/ui/global_error/global_error.h"
26 #include "chrome/browser/ui/global_error/global_error_service.h"
27 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
28 #include "chrome/browser/ui/host_desktop.h"
29 #include "chrome/common/extensions/extension.h"
30 #include "chrome/common/extensions/extension_constants.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 "grit/chromium_strings.h"
37 #include "grit/generated_resources.h"
38 #include "grit/theme_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 #include "ui/gfx/size.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 // TODO(mpcomplete): Get rid of the refcounting on this class, or document
60 // why it's necessary. Will do after refactoring to merge back with
61 // ExtensionDisabledDialogDelegate.
62 class ExternalInstallDialogDelegate
63     : public ExtensionInstallPrompt::Delegate,
64       public base::RefCountedThreadSafe<ExternalInstallDialogDelegate> {
65  public:
66   ExternalInstallDialogDelegate(Browser* browser,
67                                 ExtensionService* service,
68                                 const Extension* extension,
69                                 bool use_global_error);
70
71   Browser* browser() { return browser_; }
72
73  private:
74   friend class base::RefCountedThreadSafe<ExternalInstallDialogDelegate>;
75   friend class ExternalInstallGlobalError;
76
77   virtual ~ExternalInstallDialogDelegate();
78
79   // ExtensionInstallPrompt::Delegate:
80   virtual void InstallUIProceed() OVERRIDE;
81   virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
82
83   // The UI for showing the install dialog when enabling.
84   scoped_ptr<ExtensionInstallPrompt> install_ui_;
85
86   Browser* browser_;
87   base::WeakPtr<ExtensionService> service_weak_;
88   const std::string extension_id_;
89 };
90
91 // Only shows a menu item, no bubble. Clicking the menu item shows
92 // an external install dialog.
93 class ExternalInstallMenuAlert : public GlobalErrorWithStandardBubble,
94                                  public content::NotificationObserver {
95  public:
96   ExternalInstallMenuAlert(ExtensionService* service,
97                            const Extension* extension);
98   virtual ~ExternalInstallMenuAlert();
99
100   const Extension* extension() const { return extension_; }
101
102   // GlobalError implementation.
103   virtual Severity GetSeverity() OVERRIDE;
104   virtual bool HasMenuItem() OVERRIDE;
105   virtual int MenuItemCommandID() OVERRIDE;
106   virtual string16 MenuItemLabel() OVERRIDE;
107   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
108   virtual bool HasBubbleView() OVERRIDE;
109   virtual string16 GetBubbleViewTitle() OVERRIDE;
110   virtual std::vector<string16> GetBubbleViewMessages() OVERRIDE;
111   virtual string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
112   virtual string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
113   virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
114   virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
115   virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
116
117   // content::NotificationObserver implementation.
118   virtual void Observe(int type,
119                        const content::NotificationSource& source,
120                        const content::NotificationDetails& details) OVERRIDE;
121
122  protected:
123   ExtensionService* service_;
124   const Extension* extension_;
125   content::NotificationRegistrar registrar_;
126 };
127
128 // Shows a menu item and a global error bubble, replacing the install dialog.
129 class ExternalInstallGlobalError : public ExternalInstallMenuAlert {
130  public:
131   ExternalInstallGlobalError(ExtensionService* service,
132                              const Extension* extension,
133                              ExternalInstallDialogDelegate* delegate,
134                              const ExtensionInstallPrompt::Prompt& prompt);
135   virtual ~ExternalInstallGlobalError();
136
137   virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
138   virtual bool HasBubbleView() OVERRIDE;
139   virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
140   virtual string16 GetBubbleViewTitle() OVERRIDE;
141   virtual std::vector<string16> GetBubbleViewMessages() OVERRIDE;
142   virtual string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
143   virtual string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
144   virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
145   virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
146   virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
147
148  protected:
149   // Ref-counted, but needs to be disposed of if we are dismissed without
150   // having been clicked (perhaps because the user enabled the extension
151   // manually).
152   ExternalInstallDialogDelegate* delegate_;
153   const ExtensionInstallPrompt::Prompt* prompt_;
154 };
155
156 static void CreateExternalInstallGlobalError(
157     base::WeakPtr<ExtensionService> service,
158     const std::string& extension_id,
159     const ExtensionInstallPrompt::ShowParams& show_params,
160     ExtensionInstallPrompt::Delegate* prompt_delegate,
161     const ExtensionInstallPrompt::Prompt& prompt) {
162   if (!service.get())
163     return;
164   const Extension* extension = service->GetInstalledExtension(extension_id);
165   if (!extension)
166     return;
167   GlobalErrorService* error_service =
168       GlobalErrorServiceFactory::GetForProfile(service->profile());
169   if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
170     return;
171
172   ExternalInstallDialogDelegate* delegate =
173       static_cast<ExternalInstallDialogDelegate*>(prompt_delegate);
174   ExternalInstallGlobalError* error_bubble = new ExternalInstallGlobalError(
175       service.get(), extension, delegate, prompt);
176   error_service->AddGlobalError(error_bubble);
177   // Show bubble immediately if possible.
178   if (delegate->browser())
179     error_bubble->ShowBubbleView(delegate->browser());
180 }
181
182 static void ShowExternalInstallDialog(
183     ExtensionService* service,
184     Browser* browser,
185     const Extension* extension) {
186   // This object manages its own lifetime.
187   new ExternalInstallDialogDelegate(browser, service, extension, false);
188 }
189
190 // ExternalInstallDialogDelegate --------------------------------------------
191
192 ExternalInstallDialogDelegate::ExternalInstallDialogDelegate(
193     Browser* browser,
194     ExtensionService* service,
195     const Extension* extension,
196     bool use_global_error)
197     : browser_(browser),
198       service_weak_(service->AsWeakPtr()),
199       extension_id_(extension->id()) {
200   AddRef();  // Balanced in Proceed or Abort.
201
202   install_ui_.reset(
203       ExtensionInstallUI::CreateInstallPromptWithBrowser(browser));
204
205   const ExtensionInstallPrompt::ShowDialogCallback callback =
206       use_global_error ?
207       base::Bind(&CreateExternalInstallGlobalError,
208                  service_weak_, extension_id_) :
209       ExtensionInstallPrompt::GetDefaultShowDialogCallback();
210   install_ui_->ConfirmExternalInstall(this, extension, callback);
211 }
212
213 ExternalInstallDialogDelegate::~ExternalInstallDialogDelegate() {
214 }
215
216 void ExternalInstallDialogDelegate::InstallUIProceed() {
217   if (!service_weak_.get())
218     return;
219   const Extension* extension =
220       service_weak_->GetInstalledExtension(extension_id_);
221   if (!extension)
222     return;
223   service_weak_->GrantPermissionsAndEnableExtension(extension);
224   Release();
225 }
226
227 void ExternalInstallDialogDelegate::InstallUIAbort(bool user_initiated) {
228   if (!service_weak_.get())
229     return;
230   const Extension* extension =
231       service_weak_->GetInstalledExtension(extension_id_);
232   if (!extension)
233     return;
234   service_weak_->UninstallExtension(extension_id_, false, NULL);
235   Release();
236 }
237
238 // ExternalInstallMenuAlert -------------------------------------------------
239
240 ExternalInstallMenuAlert::ExternalInstallMenuAlert(
241     ExtensionService* service,
242     const Extension* extension)
243     : service_(service),
244       extension_(extension) {
245   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
246                  content::Source<Profile>(service->profile()));
247   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED,
248                  content::Source<Profile>(service->profile()));
249 }
250
251 ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
252 }
253
254 GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
255   return SEVERITY_LOW;
256 }
257
258 bool ExternalInstallMenuAlert::HasMenuItem() {
259   return true;
260 }
261
262 int ExternalInstallMenuAlert::MenuItemCommandID() {
263   return kMenuCommandId;
264 }
265
266 string16 ExternalInstallMenuAlert::MenuItemLabel() {
267   int id = -1;
268   if (extension_->is_app())
269     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
270   else if (extension_->is_theme())
271     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
272   else
273     id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
274   return l10n_util::GetStringFUTF16(id, UTF8ToUTF16(extension_->name()));
275 }
276
277 void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
278   ShowExternalInstallDialog(service_, browser, extension_);
279 }
280
281 bool ExternalInstallMenuAlert::HasBubbleView() {
282   return false;
283 }
284 string16 ExternalInstallMenuAlert::GetBubbleViewTitle() {
285   return string16();
286 }
287
288 std::vector<string16> ExternalInstallMenuAlert::GetBubbleViewMessages() {
289   return std::vector<string16>();
290 }
291
292 string16 ExternalInstallMenuAlert::GetBubbleViewAcceptButtonLabel() {
293   return string16();
294 }
295
296 string16 ExternalInstallMenuAlert::GetBubbleViewCancelButtonLabel() {
297   return string16();
298 }
299
300 void ExternalInstallMenuAlert::OnBubbleViewDidClose(Browser* browser) {
301   NOTREACHED();
302 }
303
304 void ExternalInstallMenuAlert::BubbleViewAcceptButtonPressed(
305     Browser* browser) {
306   NOTREACHED();
307 }
308
309 void ExternalInstallMenuAlert::BubbleViewCancelButtonPressed(
310     Browser* browser) {
311   NOTREACHED();
312 }
313
314 void ExternalInstallMenuAlert::Observe(
315     int type,
316     const content::NotificationSource& source,
317     const content::NotificationDetails& details) {
318   // The error is invalidated if the extension has been loaded or removed.
319   DCHECK(type == chrome::NOTIFICATION_EXTENSION_LOADED ||
320          type == chrome::NOTIFICATION_EXTENSION_REMOVED);
321   const Extension* extension = content::Details<const Extension>(details).ptr();
322   if (extension != extension_)
323     return;
324   GlobalErrorService* error_service =
325       GlobalErrorServiceFactory::GetForProfile(service_->profile());
326   error_service->RemoveGlobalError(this);
327   service_->AcknowledgeExternalExtension(extension_->id());
328   delete this;
329 }
330
331 // ExternalInstallGlobalError -----------------------------------------------
332
333 ExternalInstallGlobalError::ExternalInstallGlobalError(
334     ExtensionService* service,
335     const Extension* extension,
336     ExternalInstallDialogDelegate* delegate,
337     const ExtensionInstallPrompt::Prompt& prompt)
338     : ExternalInstallMenuAlert(service, extension),
339       delegate_(delegate),
340       prompt_(&prompt) {
341 }
342
343 ExternalInstallGlobalError::~ExternalInstallGlobalError() {
344   if (delegate_)
345     delegate_->Release();
346 }
347
348 void ExternalInstallGlobalError::ExecuteMenuItem(Browser* browser) {
349   ShowBubbleView(browser);
350 }
351
352 bool ExternalInstallGlobalError::HasBubbleView() {
353   return true;
354 }
355
356 gfx::Image ExternalInstallGlobalError::GetBubbleViewIcon() {
357   if (prompt_->icon().IsEmpty())
358     return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
359   // Scale icon to a reasonable size.
360   return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
361       *prompt_->icon().ToImageSkia(),
362       skia::ImageOperations::RESIZE_BEST,
363       gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
364                 extension_misc::EXTENSION_ICON_SMALL)));
365 }
366
367 string16 ExternalInstallGlobalError::GetBubbleViewTitle() {
368   return prompt_->GetDialogTitle();
369 }
370
371 std::vector<string16> ExternalInstallGlobalError::GetBubbleViewMessages() {
372   std::vector<string16> messages;
373   messages.push_back(prompt_->GetHeading());
374   if (prompt_->GetPermissionCount()) {
375     messages.push_back(prompt_->GetPermissionsHeading());
376     for (size_t i = 0; i < prompt_->GetPermissionCount(); ++i) {
377       messages.push_back(l10n_util::GetStringFUTF16(
378           IDS_EXTENSION_PERMISSION_LINE,
379           prompt_->GetPermission(i)));
380     }
381   }
382   // TODO(yoz): OAuth issue advice?
383   return messages;
384 }
385
386 string16 ExternalInstallGlobalError::GetBubbleViewAcceptButtonLabel() {
387   return prompt_->GetAcceptButtonLabel();
388 }
389
390 string16 ExternalInstallGlobalError::GetBubbleViewCancelButtonLabel() {
391   return prompt_->GetAbortButtonLabel();
392 }
393
394 void ExternalInstallGlobalError::OnBubbleViewDidClose(Browser* browser) {
395 }
396
397 void ExternalInstallGlobalError::BubbleViewAcceptButtonPressed(
398     Browser* browser) {
399   ExternalInstallDialogDelegate* delegate = delegate_;
400   delegate_ = NULL;
401   delegate->InstallUIProceed();
402 }
403
404 void ExternalInstallGlobalError::BubbleViewCancelButtonPressed(
405     Browser* browser) {
406   ExternalInstallDialogDelegate* delegate = delegate_;
407   delegate_ = NULL;
408   delegate->InstallUIAbort(true);
409 }
410
411 // Public interface ---------------------------------------------------------
412
413 void AddExternalInstallError(ExtensionService* service,
414                              const Extension* extension,
415                              bool is_new_profile) {
416   GlobalErrorService* error_service =
417       GlobalErrorServiceFactory::GetForProfile(service->profile());
418   if (error_service->GetGlobalErrorByMenuItemCommandID(kMenuCommandId))
419     return;
420
421   if (UseBubbleInstall(extension, is_new_profile)) {
422     Browser* browser = NULL;
423 #if !defined(OS_ANDROID)
424     browser = chrome::FindTabbedBrowser(service->profile(),
425                                         true,
426                                         chrome::GetActiveDesktop());
427 #endif
428     new ExternalInstallDialogDelegate(browser, service, extension, true);
429   } else {
430     error_service->AddGlobalError(
431         new ExternalInstallMenuAlert(service, extension));
432   }
433 }
434
435 void RemoveExternalInstallError(ExtensionService* service) {
436   GlobalErrorService* error_service =
437       GlobalErrorServiceFactory::GetForProfile(service->profile());
438   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
439       kMenuCommandId);
440   if (error) {
441     error_service->RemoveGlobalError(error);
442     delete error;
443   }
444 }
445
446 bool HasExternalInstallError(ExtensionService* service) {
447   GlobalErrorService* error_service =
448       GlobalErrorServiceFactory::GetForProfile(service->profile());
449   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
450       kMenuCommandId);
451   return !!error;
452 }
453
454 bool HasExternalInstallBubble(ExtensionService* service) {
455   GlobalErrorService* error_service =
456       GlobalErrorServiceFactory::GetForProfile(service->profile());
457   GlobalError* error = error_service->GetGlobalErrorByMenuItemCommandID(
458       kMenuCommandId);
459   return error && error->HasBubbleView();
460 }
461
462 }  // namespace extensions