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