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