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/extension_disabled_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/image_loader.h"
23#include "chrome/browser/profiles/profile.h"
24#include "chrome/browser/ui/browser.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/tabs/tab_strip_model.h"
29#include "chrome/common/extensions/extension_icon_set.h"
30#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
31#include "content/public/browser/notification_details.h"
32#include "content/public/browser/notification_observer.h"
33#include "content/public/browser/notification_registrar.h"
34#include "content/public/browser/notification_source.h"
35#include "extensions/common/extension.h"
36#include "extensions/common/permissions/permission_message_provider.h"
37#include "extensions/common/permissions/permission_set.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
46using extensions::Extension;
47
48namespace {
49
50static const int kIconSize = extension_misc::EXTENSION_ICON_SMALL;
51
52static base::LazyInstance<
53    std::bitset<IDC_EXTENSION_DISABLED_LAST -
54                IDC_EXTENSION_DISABLED_FIRST + 1> >
55    menu_command_ids = LAZY_INSTANCE_INITIALIZER;
56
57// Get an available menu ID.
58int GetMenuCommandID() {
59  int id;
60  for (id = IDC_EXTENSION_DISABLED_FIRST;
61       id <= IDC_EXTENSION_DISABLED_LAST; ++id) {
62    if (!menu_command_ids.Get()[id - IDC_EXTENSION_DISABLED_FIRST]) {
63      menu_command_ids.Get().set(id - IDC_EXTENSION_DISABLED_FIRST);
64      return id;
65    }
66  }
67  // This should not happen.
68  DCHECK(id <= IDC_EXTENSION_DISABLED_LAST) <<
69      "No available menu command IDs for ExtensionDisabledGlobalError";
70  return IDC_EXTENSION_DISABLED_LAST;
71}
72
73// Make a menu ID available when it is no longer used.
74void ReleaseMenuCommandID(int id) {
75  menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST);
76}
77
78}  // namespace
79
80// ExtensionDisabledDialogDelegate --------------------------------------------
81
82class ExtensionDisabledDialogDelegate
83    : public ExtensionInstallPrompt::Delegate,
84      public base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate> {
85 public:
86  ExtensionDisabledDialogDelegate(ExtensionService* service,
87                                  scoped_ptr<ExtensionInstallPrompt> install_ui,
88                                  const Extension* extension);
89
90 private:
91  friend class base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate>;
92
93  virtual ~ExtensionDisabledDialogDelegate();
94
95  // ExtensionInstallPrompt::Delegate:
96  virtual void InstallUIProceed() OVERRIDE;
97  virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
98
99  // The UI for showing the install dialog when enabling.
100  scoped_ptr<ExtensionInstallPrompt> install_ui_;
101
102  ExtensionService* service_;
103  const Extension* extension_;
104};
105
106ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate(
107    ExtensionService* service,
108    scoped_ptr<ExtensionInstallPrompt> install_ui,
109    const Extension* extension)
110    : install_ui_(install_ui.Pass()),
111      service_(service),
112      extension_(extension) {
113  AddRef();  // Balanced in Proceed or Abort.
114  install_ui_->ConfirmReEnable(this, extension_);
115}
116
117ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
118}
119
120void ExtensionDisabledDialogDelegate::InstallUIProceed() {
121  service_->GrantPermissionsAndEnableExtension(extension_);
122  Release();
123}
124
125void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated) {
126  std::string histogram_name = user_initiated ?
127      "Extensions.Permissions_ReEnableCancel" :
128      "Extensions.Permissions_ReEnableAbort";
129  ExtensionService::RecordPermissionMessagesHistogram(
130      extension_, histogram_name.c_str());
131
132  // Do nothing. The extension will remain disabled.
133  Release();
134}
135
136// ExtensionDisabledGlobalError -----------------------------------------------
137
138class ExtensionDisabledGlobalError : public GlobalErrorWithStandardBubble,
139                                     public content::NotificationObserver,
140                                     public ExtensionUninstallDialog::Delegate {
141 public:
142  ExtensionDisabledGlobalError(ExtensionService* service,
143                               const Extension* extension,
144                               const gfx::Image& icon);
145  virtual ~ExtensionDisabledGlobalError();
146
147  // GlobalError implementation.
148  virtual Severity GetSeverity() OVERRIDE;
149  virtual bool HasMenuItem() OVERRIDE;
150  virtual int MenuItemCommandID() OVERRIDE;
151  virtual base::string16 MenuItemLabel() OVERRIDE;
152  virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
153  virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
154  virtual base::string16 GetBubbleViewTitle() OVERRIDE;
155  virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
156  virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
157  virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
158  virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
159  virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
160  virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
161
162  // ExtensionUninstallDialog::Delegate implementation.
163  virtual void ExtensionUninstallAccepted() OVERRIDE;
164  virtual void ExtensionUninstallCanceled() OVERRIDE;
165
166  // content::NotificationObserver implementation.
167  virtual void Observe(int type,
168                       const content::NotificationSource& source,
169                       const content::NotificationDetails& details) OVERRIDE;
170
171 private:
172  ExtensionService* service_;
173  const Extension* extension_;
174  gfx::Image icon_;
175
176  // How the user responded to the error; used for metrics.
177  enum UserResponse {
178    IGNORED,
179    REENABLE,
180    UNINSTALL,
181    EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
182  };
183  UserResponse user_response_;
184
185  scoped_ptr<ExtensionUninstallDialog> uninstall_dialog_;
186
187  // Menu command ID assigned for this extension's error.
188  int menu_command_id_;
189
190  content::NotificationRegistrar registrar_;
191};
192
193// TODO(yoz): create error at startup for disabled extensions.
194ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
195    ExtensionService* service,
196    const Extension* extension,
197    const gfx::Image& icon)
198    : service_(service),
199      extension_(extension),
200      icon_(icon),
201      user_response_(IGNORED),
202      menu_command_id_(GetMenuCommandID()) {
203  if (icon_.IsEmpty()) {
204    icon_ = gfx::Image(
205        gfx::ImageSkiaOperations::CreateResizedImage(
206            extension_->is_app() ?
207                extensions::IconsInfo::GetDefaultAppIcon() :
208                extensions::IconsInfo::GetDefaultExtensionIcon(),
209            skia::ImageOperations::RESIZE_BEST,
210            gfx::Size(kIconSize, kIconSize)));
211  }
212  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
213                 content::Source<Profile>(service->profile()));
214  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_REMOVED,
215                 content::Source<Profile>(service->profile()));
216}
217
218ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {
219  ReleaseMenuCommandID(menu_command_id_);
220  UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
221                            user_response_,
222                            EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
223}
224
225GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() {
226  return SEVERITY_LOW;
227}
228
229bool ExtensionDisabledGlobalError::HasMenuItem() {
230  return true;
231}
232
233int ExtensionDisabledGlobalError::MenuItemCommandID() {
234  return menu_command_id_;
235}
236
237base::string16 ExtensionDisabledGlobalError::MenuItemLabel() {
238  return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
239                                    UTF8ToUTF16(extension_->name()));
240}
241
242void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) {
243  ShowBubbleView(browser);
244}
245
246gfx::Image ExtensionDisabledGlobalError::GetBubbleViewIcon() {
247  return icon_;
248}
249
250base::string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() {
251  return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
252                                    UTF8ToUTF16(extension_->name()));
253}
254
255std::vector<base::string16>
256ExtensionDisabledGlobalError::GetBubbleViewMessages() {
257  std::vector<base::string16> messages;
258  messages.push_back(l10n_util::GetStringFUTF16(
259      extension_->is_app() ?
260      IDS_APP_DISABLED_ERROR_LABEL : IDS_EXTENSION_DISABLED_ERROR_LABEL,
261      UTF8ToUTF16(extension_->name())));
262  messages.push_back(l10n_util::GetStringUTF16(
263      IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO));
264  std::vector<base::string16> permission_warnings =
265      extensions::PermissionMessageProvider::Get()->GetWarningMessages(
266          extension_->GetActivePermissions(), extension_->GetType());
267  for (size_t i = 0; i < permission_warnings.size(); ++i) {
268    messages.push_back(l10n_util::GetStringFUTF16(
269        IDS_EXTENSION_PERMISSION_LINE, permission_warnings[i]));
270  }
271  return messages;
272}
273
274base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
275  return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON);
276}
277
278base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
279  return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL);
280}
281
282void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
283}
284
285void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
286    Browser* browser) {
287  // Delay extension reenabling so this bubble closes properly.
288  base::MessageLoop::current()->PostTask(FROM_HERE,
289      base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension,
290                 service_->AsWeakPtr(), extension_));
291}
292
293void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
294    Browser* browser) {
295#if !defined(OS_ANDROID)
296  uninstall_dialog_.reset(
297      ExtensionUninstallDialog::Create(service_->profile(), browser, this));
298  // Delay showing the uninstall dialog, so that this function returns
299  // immediately, to close the bubble properly. See crbug.com/121544.
300  base::MessageLoop::current()->PostTask(FROM_HERE,
301      base::Bind(&ExtensionUninstallDialog::ConfirmUninstall,
302                 uninstall_dialog_->AsWeakPtr(), extension_));
303#endif  // !defined(OS_ANDROID)
304}
305
306void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() {
307  service_->UninstallExtension(extension_->id(), false, NULL);
308}
309
310void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() {
311  // Nothing happens, and the error is still there.
312}
313
314void ExtensionDisabledGlobalError::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  GlobalErrorServiceFactory::GetForProfile(service_->profile())->
325      RemoveGlobalError(this);
326
327  if (type == chrome::NOTIFICATION_EXTENSION_LOADED)
328    user_response_ = REENABLE;
329  else if (type == chrome::NOTIFICATION_EXTENSION_REMOVED)
330    user_response_ = UNINSTALL;
331  delete this;
332}
333
334// Globals --------------------------------------------------------------------
335
336namespace extensions {
337
338void AddExtensionDisabledErrorWithIcon(base::WeakPtr<ExtensionService> service,
339                                       const std::string& extension_id,
340                                       const gfx::Image& icon) {
341  if (!service.get())
342    return;
343  const Extension* extension = service->GetInstalledExtension(extension_id);
344  if (extension) {
345    GlobalErrorServiceFactory::GetForProfile(service->profile())
346        ->AddGlobalError(
347              new ExtensionDisabledGlobalError(service.get(), extension, icon));
348  }
349}
350
351void AddExtensionDisabledError(ExtensionService* service,
352                               const Extension* extension) {
353  extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
354      extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
355  gfx::Size size(kIconSize, kIconSize);
356  ImageLoader::Get(service->profile())->LoadImageAsync(
357      extension, image, size,
358      base::Bind(&AddExtensionDisabledErrorWithIcon,
359                 service->AsWeakPtr(), extension->id()));
360}
361
362void ShowExtensionDisabledDialog(ExtensionService* service,
363                                 content::WebContents* web_contents,
364                                 const Extension* extension) {
365  scoped_ptr<ExtensionInstallPrompt> install_ui(
366      new ExtensionInstallPrompt(web_contents));
367  // This object manages its own lifetime.
368  new ExtensionDisabledDialogDelegate(service, install_ui.Pass(), extension);
369}
370
371}  // namespace extensions
372