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