blob: 540fc6cd280917b9bfa36321a07d3d956ad12959 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_disabled_ui.h"
#include <string>
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/global_error/global_error.h"
#include "chrome/browser/ui/global_error/global_error_service.h"
#include "chrome/browser/ui/global_error/global_error_service_factory.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_source.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources_standard.h"
#include "ui/base/l10n/l10n_util.h"
using extensions::Extension;
namespace {
static base::LazyInstance<
std::bitset<IDC_EXTENSION_DISABLED_LAST -
IDC_EXTENSION_DISABLED_FIRST + 1> >
menu_command_ids = LAZY_INSTANCE_INITIALIZER;
// Get an available menu ID.
int GetMenuCommandID() {
int id;
for (id = IDC_EXTENSION_DISABLED_FIRST;
id <= IDC_EXTENSION_DISABLED_LAST; ++id) {
if (!menu_command_ids.Get()[id - IDC_EXTENSION_DISABLED_FIRST]) {
menu_command_ids.Get().set(id - IDC_EXTENSION_DISABLED_FIRST);
return id;
}
}
// This should not happen.
DCHECK(id <= IDC_EXTENSION_DISABLED_LAST) <<
"No available menu command IDs for ExtensionDisabledGlobalError";
return IDC_EXTENSION_DISABLED_LAST;
}
// Make a menu ID available when it is no longer used.
void ReleaseMenuCommandID(int id) {
menu_command_ids.Get().reset(id - IDC_EXTENSION_DISABLED_FIRST);
}
} // namespace
// ExtensionDisabledDialogDelegate --------------------------------------------
class ExtensionDisabledDialogDelegate
: public ExtensionInstallPrompt::Delegate,
public base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate> {
public:
ExtensionDisabledDialogDelegate(Browser* browser,
ExtensionService* service,
const Extension* extension);
private:
friend class base::RefCountedThreadSafe<ExtensionDisabledDialogDelegate>;
virtual ~ExtensionDisabledDialogDelegate();
// ExtensionInstallPrompt::Delegate:
virtual void InstallUIProceed() OVERRIDE;
virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
// The UI for showing the install dialog when enabling.
scoped_ptr<ExtensionInstallPrompt> install_ui_;
ExtensionService* service_;
const Extension* extension_;
};
ExtensionDisabledDialogDelegate::ExtensionDisabledDialogDelegate(
Browser* browser,
ExtensionService* service,
const Extension* extension)
: service_(service), extension_(extension) {
AddRef(); // Balanced in Proceed or Abort.
install_ui_.reset(chrome::CreateExtensionInstallPromptWithBrowser(browser));
install_ui_->ConfirmReEnable(this, extension_);
}
ExtensionDisabledDialogDelegate::~ExtensionDisabledDialogDelegate() {
}
void ExtensionDisabledDialogDelegate::InstallUIProceed() {
service_->GrantPermissionsAndEnableExtension(
extension_, install_ui_->record_oauth2_grant());
Release();
}
void ExtensionDisabledDialogDelegate::InstallUIAbort(bool user_initiated) {
std::string histogram_name = user_initiated ?
"Extensions.Permissions_ReEnableCancel" :
"Extensions.Permissions_ReEnableAbort";
ExtensionService::RecordPermissionMessagesHistogram(
extension_, histogram_name.c_str());
// Do nothing. The extension will remain disabled.
Release();
}
// ExtensionDisabledGlobalError -----------------------------------------------
class ExtensionDisabledGlobalError : public GlobalError,
public content::NotificationObserver,
public ExtensionUninstallDialog::Delegate {
public:
ExtensionDisabledGlobalError(ExtensionService* service,
const Extension* extension);
virtual ~ExtensionDisabledGlobalError();
// GlobalError implementation.
virtual bool HasBadge() OVERRIDE;
virtual int GetBadgeResourceID() OVERRIDE;
virtual bool HasMenuItem() OVERRIDE;
virtual int MenuItemCommandID() OVERRIDE;
virtual string16 MenuItemLabel() OVERRIDE;
virtual int MenuItemIconResourceID() OVERRIDE;
virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
virtual bool HasBubbleView() OVERRIDE;
virtual string16 GetBubbleViewTitle() OVERRIDE;
virtual string16 GetBubbleViewMessage() OVERRIDE;
virtual string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
virtual string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
// ExtensionUninstallDialog::Delegate implementation.
virtual void ExtensionUninstallAccepted() OVERRIDE;
virtual void ExtensionUninstallCanceled() OVERRIDE;
// content::NotificationObserver implementation.
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
private:
ExtensionService* service_;
const Extension* extension_;
// How the user responded to the error; used for metrics.
enum UserResponse {
IGNORED,
REENABLE,
UNINSTALL,
EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
};
UserResponse user_response_;
scoped_ptr<ExtensionUninstallDialog> uninstall_dialog_;
// Menu command ID assigned for this extension's error.
int menu_command_id_;
content::NotificationRegistrar registrar_;
};
// TODO(yoz): create error at startup for disabled extensions.
ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
ExtensionService* service,
const Extension* extension)
: service_(service),
extension_(extension),
user_response_(IGNORED),
menu_command_id_(GetMenuCommandID()) {
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
content::Source<Profile>(service->profile()));
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
content::Source<Profile>(service->profile()));
}
ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {
ReleaseMenuCommandID(menu_command_id_);
HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse",
user_response_, EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
}
bool ExtensionDisabledGlobalError::HasBadge() {
return true;
}
int ExtensionDisabledGlobalError::GetBadgeResourceID() {
return IDR_UPDATE_BADGE;
}
bool ExtensionDisabledGlobalError::HasMenuItem() {
return true;
}
int ExtensionDisabledGlobalError::MenuItemCommandID() {
return menu_command_id_;
}
int ExtensionDisabledGlobalError::MenuItemIconResourceID() {
return IDR_UPDATE_MENU;
}
string16 ExtensionDisabledGlobalError::MenuItemLabel() {
return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
UTF8ToUTF16(extension_->name()));
}
void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) {
ShowBubbleView(browser);
}
bool ExtensionDisabledGlobalError::HasBubbleView() {
return true;
}
string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() {
return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
UTF8ToUTF16(extension_->name()));
}
string16 ExtensionDisabledGlobalError::GetBubbleViewMessage() {
return l10n_util::GetStringFUTF16(extension_->is_app() ?
IDS_APP_DISABLED_ERROR_LABEL : IDS_EXTENSION_DISABLED_ERROR_LABEL,
UTF8ToUTF16(extension_->name()));
}
string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
return l10n_util::GetStringUTF16(
IDS_EXTENSION_DISABLED_ERROR_ENABLE_BUTTON);
}
string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL);
}
void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
}
void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
Browser* browser) {
new ExtensionDisabledDialogDelegate(browser, service_, extension_);
}
void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
Browser* browser) {
#if !defined(OS_ANDROID)
uninstall_dialog_.reset(
ExtensionUninstallDialog::Create(browser, this));
// Delay showing the uninstall dialog, so that this function returns
// immediately, to close the bubble properly. See crbug.com/121544.
MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&ExtensionUninstallDialog::ConfirmUninstall,
uninstall_dialog_->AsWeakPtr(), extension_));
#endif // !defined(OS_ANDROID)
}
void ExtensionDisabledGlobalError::ExtensionUninstallAccepted() {
service_->UninstallExtension(extension_->id(), false, NULL);
}
void ExtensionDisabledGlobalError::ExtensionUninstallCanceled() {
// Nothing happens, and the error is still there.
}
void ExtensionDisabledGlobalError::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
const Extension* extension = NULL;
// The error is invalidated if the extension has been reloaded
// or unloaded.
if (type == chrome::NOTIFICATION_EXTENSION_LOADED) {
extension = content::Details<const Extension>(details).ptr();
} else {
DCHECK_EQ(chrome::NOTIFICATION_EXTENSION_UNLOADED, type);
extensions::UnloadedExtensionInfo* info =
content::Details<extensions::UnloadedExtensionInfo>(details).ptr();
extension = info->extension;
}
if (extension == extension_) {
GlobalErrorServiceFactory::GetForProfile(service_->profile())->
RemoveGlobalError(this);
if (type == chrome::NOTIFICATION_EXTENSION_LOADED)
user_response_ = REENABLE;
else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED)
user_response_ = UNINSTALL;
delete this;
}
}
// Globals --------------------------------------------------------------------
namespace extensions {
void AddExtensionDisabledError(ExtensionService* service,
const Extension* extension) {
GlobalErrorServiceFactory::GetForProfile(service->profile())->
AddGlobalError(new ExtensionDisabledGlobalError(service, extension));
}
void ShowExtensionDisabledDialog(ExtensionService* service, Browser* browser,
const Extension* extension) {
// This object manages its own lifetime.
new ExtensionDisabledDialogDelegate(browser, service, extension);
}
} // namespace extensions