Update broken references to image assets
[chromium-blink-merge.git] / chrome / browser / extensions / extension_disabled_ui.cc
blob99eaff724519829249c2b476dfa8fea0cd0b827f
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.
5 #include "chrome/browser/extensions/extension_disabled_ui.h"
7 #include <bitset>
8 #include <string>
10 #include "base/bind.h"
11 #include "base/lazy_instance.h"
12 #include "base/location.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/metrics/histogram.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "chrome/app/chrome_command_ids.h"
21 #include "chrome/browser/extensions/extension_install_prompt.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
24 #include "chrome/browser/extensions/extension_util.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_window.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/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/grit/chromium_strings.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "content/public/browser/notification_details.h"
35 #include "content/public/browser/notification_observer.h"
36 #include "content/public/browser/notification_registrar.h"
37 #include "content/public/browser/notification_source.h"
38 #include "extensions/browser/extension_util.h"
39 #include "extensions/browser/image_loader.h"
40 #include "extensions/browser/notification_types.h"
41 #include "extensions/browser/uninstall_reason.h"
42 #include "extensions/common/constants.h"
43 #include "extensions/common/extension.h"
44 #include "extensions/common/extension_icon_set.h"
45 #include "extensions/common/manifest_handlers/icons_handler.h"
46 #include "extensions/common/permissions/coalesced_permission_message.h"
47 #include "extensions/common/permissions/permissions_data.h"
48 #include "ui/base/l10n/l10n_util.h"
49 #include "ui/gfx/geometry/size.h"
50 #include "ui/gfx/image/image.h"
51 #include "ui/gfx/image/image_skia_operations.h"
53 using extensions::CoalescedPermissionMessage;
54 using extensions::CoalescedPermissionMessages;
55 using extensions::Extension;
57 namespace {
59 static const int kIconSize = extension_misc::EXTENSION_ICON_SMALL;
61 static base::LazyInstance<
62 std::bitset<IDC_EXTENSION_DISABLED_LAST -
63 IDC_EXTENSION_DISABLED_FIRST + 1> >
64 menu_command_ids = LAZY_INSTANCE_INITIALIZER;
66 // Get an available menu ID.
67 int GetMenuCommandID() {
68 int id;
69 for (id = IDC_EXTENSION_DISABLED_FIRST;
70 id <= IDC_EXTENSION_DISABLED_LAST; ++id) {
71 if (!menu_command_ids.Get()[id - IDC_EXTENSION_DISABLED_FIRST]) {
72 menu_command_ids.Get().set(id - IDC_EXTENSION_DISABLED_FIRST);
73 return id;
76 // This should not happen.
77 DCHECK(id <= IDC_EXTENSION_DISABLED_LAST) <<
78 "No available menu command IDs for ExtensionDisabledGlobalError";
79 return IDC_EXTENSION_DISABLED_LAST;
82 // Make a menu ID available when it is no longer used.
83 void ReleaseMenuCommandID(int id) {
84 menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST);
87 } // namespace
89 // ExtensionDisabledDialogDelegate --------------------------------------------
91 class ExtensionDisabledDialogDelegate
92 : public ExtensionInstallPrompt::Delegate,
93 public base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate> {
94 public:
95 ExtensionDisabledDialogDelegate(ExtensionService* service,
96 scoped_ptr<ExtensionInstallPrompt> install_ui,
97 const Extension* extension);
99 private:
100 friend class base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate>;
102 ~ExtensionDisabledDialogDelegate() override;
104 // ExtensionInstallPrompt::Delegate:
105 void InstallUIProceed() override;
106 void InstallUIAbort(bool user_initiated) override;
108 // The UI for showing the install dialog when enabling.
109 scoped_ptr<ExtensionInstallPrompt> install_ui_;
111 ExtensionService* service_;
112 const Extension* extension_;
115 ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate(
116 ExtensionService* service,
117 scoped_ptr<ExtensionInstallPrompt> install_ui,
118 const Extension* extension)
119 : install_ui_(install_ui.Pass()),
120 service_(service),
121 extension_(extension) {
122 AddRef(); // Balanced in Proceed or Abort.
123 install_ui_->ConfirmReEnable(this, extension_);
126 ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
129 void ExtensionDisabledDialogDelegate::InstallUIProceed() {
130 service_->GrantPermissionsAndEnableExtension(extension_);
131 Release();
134 void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated) {
135 std::string histogram_name = user_initiated ? "ReEnableCancel"
136 : "ReEnableAbort";
137 ExtensionService::RecordPermissionMessagesHistogram(
138 extension_, histogram_name.c_str());
140 // Do nothing. The extension will remain disabled.
141 Release();
144 // ExtensionDisabledGlobalError -----------------------------------------------
146 class ExtensionDisabledGlobalError
147 : public GlobalErrorWithStandardBubble,
148 public content::NotificationObserver,
149 public extensions::ExtensionUninstallDialog::Delegate {
150 public:
151 ExtensionDisabledGlobalError(ExtensionService* service,
152 const Extension* extension,
153 bool is_remote_install,
154 const gfx::Image& icon);
155 ~ExtensionDisabledGlobalError() override;
157 // GlobalError implementation.
158 Severity GetSeverity() override;
159 bool HasMenuItem() override;
160 int MenuItemCommandID() override;
161 base::string16 MenuItemLabel() override;
162 void ExecuteMenuItem(Browser* browser) override;
163 gfx::Image GetBubbleViewIcon() override;
164 base::string16 GetBubbleViewTitle() override;
165 std::vector<base::string16> GetBubbleViewMessages() override;
166 base::string16 GetBubbleViewAcceptButtonLabel() override;
167 base::string16 GetBubbleViewCancelButtonLabel() override;
168 void OnBubbleViewDidClose(Browser* browser) override;
169 void BubbleViewAcceptButtonPressed(Browser* browser) override;
170 void BubbleViewCancelButtonPressed(Browser* browser) override;
171 bool ShouldCloseOnDeactivate() const override;
173 // ExtensionUninstallDialog::Delegate implementation.
174 void OnExtensionUninstallDialogClosed(bool did_start_uninstall,
175 const base::string16& error) override;
177 // content::NotificationObserver implementation.
178 void Observe(int type,
179 const content::NotificationSource& source,
180 const content::NotificationDetails& details) override;
182 private:
183 ExtensionService* service_;
184 const Extension* extension_;
185 bool is_remote_install_;
186 gfx::Image icon_;
188 // How the user responded to the error; used for metrics.
189 enum UserResponse {
190 IGNORED,
191 REENABLE,
192 UNINSTALL,
193 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
195 UserResponse user_response_;
197 scoped_ptr<extensions::ExtensionUninstallDialog> uninstall_dialog_;
199 // Menu command ID assigned for this extension's error.
200 int menu_command_id_;
202 content::NotificationRegistrar registrar_;
205 // TODO(yoz): create error at startup for disabled extensions.
206 ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
207 ExtensionService* service,
208 const Extension* extension,
209 bool is_remote_install,
210 const gfx::Image& icon)
211 : service_(service),
212 extension_(extension),
213 is_remote_install_(is_remote_install),
214 icon_(icon),
215 user_response_(IGNORED),
216 menu_command_id_(GetMenuCommandID()) {
217 if (icon_.IsEmpty()) {
218 icon_ = gfx::Image(
219 gfx::ImageSkiaOperations::CreateResizedImage(
220 extension_->is_app() ?
221 extensions::util::GetDefaultAppIcon() :
222 extensions::util::GetDefaultExtensionIcon(),
223 skia::ImageOperations::RESIZE_BEST,
224 gfx::Size(kIconSize, kIconSize)));
226 registrar_.Add(this,
227 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
228 content::Source<Profile>(service->profile()));
229 registrar_.Add(this,
230 extensions::NOTIFICATION_EXTENSION_REMOVED,
231 content::Source<Profile>(service->profile()));
234 ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {
235 ReleaseMenuCommandID(menu_command_id_);
236 if (is_remote_install_) {
237 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponseRemoteInstall",
238 user_response_,
239 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
240 } else {
241 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
242 user_response_,
243 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
247 GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() {
248 return SEVERITY_LOW;
251 bool ExtensionDisabledGlobalError::HasMenuItem() {
252 return true;
255 int ExtensionDisabledGlobalError::MenuItemCommandID() {
256 return menu_command_id_;
259 base::string16 ExtensionDisabledGlobalError::MenuItemLabel() {
260 std::string extension_name = extension_->name();
261 // Ampersands need to be escaped to avoid being treated like
262 // mnemonics in the menu.
263 base::ReplaceChars(extension_name, "&", "&&", &extension_name);
265 if (is_remote_install_) {
266 return l10n_util::GetStringFUTF16(
267 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
268 base::UTF8ToUTF16(extension_name));
269 } else {
270 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
271 base::UTF8ToUTF16(extension_name));
275 void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) {
276 ShowBubbleView(browser);
279 gfx::Image ExtensionDisabledGlobalError::GetBubbleViewIcon() {
280 return icon_;
283 base::string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() {
284 if (is_remote_install_) {
285 return l10n_util::GetStringFUTF16(
286 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
287 base::UTF8ToUTF16(extension_->name()));
288 } else {
289 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
290 base::UTF8ToUTF16(extension_->name()));
294 std::vector<base::string16>
295 ExtensionDisabledGlobalError::GetBubbleViewMessages() {
296 std::vector<base::string16> messages;
297 CoalescedPermissionMessages permission_warnings =
298 extension_->permissions_data()->GetPermissionMessages();
299 if (is_remote_install_) {
300 messages.push_back(l10n_util::GetStringFUTF16(
301 extension_->is_app()
302 ? IDS_APP_DISABLED_REMOTE_INSTALL_ERROR_LABEL
303 : IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_LABEL,
304 base::UTF8ToUTF16(extension_->name())));
305 if (!permission_warnings.empty())
306 messages.push_back(
307 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO));
308 } else {
309 // TODO(treib): If NeedCustodianApprovalForPermissionIncrease, add an extra
310 // message for supervised users. crbug.com/461261
311 messages.push_back(l10n_util::GetStringFUTF16(
312 extension_->is_app() ? IDS_APP_DISABLED_ERROR_LABEL
313 : IDS_EXTENSION_DISABLED_ERROR_LABEL,
314 base::UTF8ToUTF16(extension_->name())));
315 messages.push_back(l10n_util::GetStringUTF16(
316 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO));
318 for (const CoalescedPermissionMessage& msg : permission_warnings) {
319 messages.push_back(l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE,
320 msg.message()));
322 return messages;
325 base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
326 if (extensions::util::IsExtensionSupervised(extension_,
327 service_->profile()) &&
328 extensions::util::NeedCustodianApprovalForPermissionIncrease()) {
329 // TODO(treib): Probably use a new string here once we get UX design.
330 // For now, just use "OK". crbug.com/461261
331 return l10n_util::GetStringUTF16(IDS_OK);
333 if (is_remote_install_) {
334 return l10n_util::GetStringUTF16(
335 extension_->is_app()
336 ? IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_APP
337 : IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_EXTENSION);
339 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON);
342 base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
343 if (extensions::util::IsExtensionSupervised(extension_,
344 service_->profile())) {
345 if (extensions::util::NeedCustodianApprovalForPermissionIncrease()) {
346 // If the supervised user can't approve the update, then there is no
347 // "cancel" button.
348 return base::string16();
349 } else {
350 // Supervised users can not remove extensions, so use "cancel" here
351 // instead of "uninstall".
352 return l10n_util::GetStringUTF16(IDS_CANCEL);
355 return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL);
358 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
361 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
362 Browser* browser) {
363 if (extensions::util::IsExtensionSupervised(extension_,
364 service_->profile()) &&
365 extensions::util::NeedCustodianApprovalForPermissionIncrease()) {
366 return;
368 // Delay extension reenabling so this bubble closes properly.
369 base::ThreadTaskRunnerHandle::Get()->PostTask(
370 FROM_HERE,
371 base::Bind(&ExtensionService::GrantPermissionsAndEnableExtension,
372 service_->AsWeakPtr(), extension_));
375 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
376 Browser* browser) {
377 if (extensions::util::IsExtensionSupervised(extension_,
378 service_->profile())) {
379 // For custodian-installed extensions, this button should only exist if the
380 // supervised user can approve the update. Otherwise there is only an "OK"
381 // button.
382 DCHECK(!extensions::util::NeedCustodianApprovalForPermissionIncrease());
383 // Supervised users may never remove custodian-installed extensions.
384 return;
387 uninstall_dialog_.reset(extensions::ExtensionUninstallDialog::Create(
388 service_->profile(), browser->window()->GetNativeWindow(), this));
389 // Delay showing the uninstall dialog, so that this function returns
390 // immediately, to close the bubble properly. See crbug.com/121544.
391 base::ThreadTaskRunnerHandle::Get()->PostTask(
392 FROM_HERE,
393 base::Bind(&extensions::ExtensionUninstallDialog::ConfirmUninstall,
394 uninstall_dialog_->AsWeakPtr(), extension_,
395 extensions::UNINSTALL_REASON_EXTENSION_DISABLED,
396 extensions::UNINSTALL_SOURCE_PERMISSIONS_INCREASE));
399 bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
400 // Since this indicates that an extension was disabled, we should definitely
401 // have the user acknowledge it, rather than having the bubble disappear when
402 // a new window pops up.
403 return false;
406 void ExtensionDisabledGlobalError::OnExtensionUninstallDialogClosed(
407 bool did_start_uninstall,
408 const base::string16& error) {
409 // No need to do anything.
412 void ExtensionDisabledGlobalError::Observe(
413 int type,
414 const content::NotificationSource& source,
415 const content::NotificationDetails& details) {
416 // The error is invalidated if the extension has been loaded or removed.
417 DCHECK(type == extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED ||
418 type == extensions::NOTIFICATION_EXTENSION_REMOVED);
419 const Extension* extension = content::Details<const Extension>(details).ptr();
420 if (extension != extension_)
421 return;
422 GlobalErrorServiceFactory::GetForProfile(service_->profile())->
423 RemoveGlobalError(this);
425 // Make sure we don't call RemoveGlobalError again.
426 registrar_.RemoveAll();
428 if (type == extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED)
429 user_response_ = REENABLE;
430 else if (type == extensions::NOTIFICATION_EXTENSION_REMOVED)
431 user_response_ = UNINSTALL;
433 // Delete this object after any running tasks, so that the extension dialog
434 // still has it as a delegate to finish the current tasks.
435 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
438 // Globals --------------------------------------------------------------------
440 namespace extensions {
442 void AddExtensionDisabledErrorWithIcon(base::WeakPtr<ExtensionService> service,
443 const std::string& extension_id,
444 bool is_remote_install,
445 const gfx::Image& icon) {
446 if (!service.get())
447 return;
448 const Extension* extension = service->GetInstalledExtension(extension_id);
449 if (extension) {
450 GlobalErrorServiceFactory::GetForProfile(service->profile())
451 ->AddGlobalError(new ExtensionDisabledGlobalError(
452 service.get(), extension, is_remote_install, icon));
456 void AddExtensionDisabledError(ExtensionService* service,
457 const Extension* extension,
458 bool is_remote_install) {
459 // Do not display notifications for ephemeral apps that have been disabled.
460 // Instead, a prompt will be shown the next time the app is launched.
461 if (util::IsEphemeralApp(extension->id(), service->profile()))
462 return;
464 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
465 extension, kIconSize, ExtensionIconSet::MATCH_BIGGER);
466 gfx::Size size(kIconSize, kIconSize);
467 ImageLoader::Get(service->profile())
468 ->LoadImageAsync(extension,
469 image,
470 size,
471 base::Bind(&AddExtensionDisabledErrorWithIcon,
472 service->AsWeakPtr(),
473 extension->id(),
474 is_remote_install));
477 void ShowExtensionDisabledDialog(ExtensionService* service,
478 content::WebContents* web_contents,
479 const Extension* extension) {
480 scoped_ptr<ExtensionInstallPrompt> install_ui(
481 new ExtensionInstallPrompt(web_contents));
482 // This object manages its own lifetime.
483 new ExtensionDisabledDialogDelegate(service, install_ui.Pass(), extension);
486 } // namespace extensions