blob: f3c99debd6c637226b84917cd331920cc42ea326 [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 <memory>
#include <string>
#include "base/bind.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/scoped_observation.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner_forward.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/extensions/extension_install_error_menu_item_id_provider.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.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/browser/ui/tabs/tab_strip_model.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/image_loader.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_icon_set.h"
#include "extensions/common/permissions/permission_message.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/size.h"
// ExtensionDisabledGlobalError -----------------------------------------------
namespace extensions {
class ExtensionDisabledGlobalError : public GlobalErrorWithStandardBubble,
public ExtensionUninstallDialog::Delegate,
public ExtensionRegistryObserver {
public:
ExtensionDisabledGlobalError(ExtensionService* service,
const Extension* extension,
bool is_remote_install);
ExtensionDisabledGlobalError(const ExtensionDisabledGlobalError&) = delete;
ExtensionDisabledGlobalError& operator=(const ExtensionDisabledGlobalError&) =
delete;
~ExtensionDisabledGlobalError() override;
// GlobalError:
Severity GetSeverity() override;
bool HasMenuItem() override;
int MenuItemCommandID() override;
std::u16string MenuItemLabel() override;
void ExecuteMenuItem(Browser* browser) override;
std::u16string GetBubbleViewTitle() override;
std::vector<std::u16string> GetBubbleViewMessages() override;
std::u16string GetBubbleViewAcceptButtonLabel() override;
std::u16string GetBubbleViewCancelButtonLabel() override;
void OnBubbleViewDidClose(Browser* browser) override;
void BubbleViewAcceptButtonPressed(Browser* browser) override;
void BubbleViewCancelButtonPressed(Browser* browser) override;
bool ShouldCloseOnDeactivate() const override;
bool ShouldShowCloseButton() const override;
// ExtensionUninstallDialog::Delegate:
void OnExtensionUninstallDialogClosed(bool did_start_uninstall,
const std::u16string& error) override;
private:
// ExtensionRegistryObserver:
void OnExtensionLoaded(content::BrowserContext* browser_context,
const Extension* extension) override;
void OnExtensionUninstalled(content::BrowserContext* browser_context,
const Extension* extension,
UninstallReason reason) override;
void OnShutdown(ExtensionRegistry* registry) override;
void RemoveGlobalError();
ExtensionService* service_;
const Extension* extension_;
bool is_remote_install_;
// How the user responded to the error; used for metrics.
enum UserResponse {
IGNORED,
REENABLE,
UNINSTALL,
EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
};
UserResponse user_response_;
std::unique_ptr<ExtensionUninstallDialog> uninstall_dialog_;
// Helper to get menu command ID assigned for this extension's error.
ExtensionInstallErrorMenuItemIdProvider id_provider_;
base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
registry_observation_{this};
};
// TODO(yoz): create error at startup for disabled extensions.
ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
ExtensionService* service,
const Extension* extension,
bool is_remote_install)
: service_(service),
extension_(extension),
is_remote_install_(is_remote_install),
user_response_(IGNORED) {
registry_observation_.Observe(ExtensionRegistry::Get(service->profile()));
}
ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {}
GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() {
return SEVERITY_LOW;
}
bool ExtensionDisabledGlobalError::HasMenuItem() {
return true;
}
int ExtensionDisabledGlobalError::MenuItemCommandID() {
return id_provider_.menu_command_id();
}
std::u16string ExtensionDisabledGlobalError::MenuItemLabel() {
std::string extension_name = extension_->name();
// Ampersands need to be escaped to avoid being treated like
// mnemonics in the menu.
base::ReplaceChars(extension_name, "&", "&&", &extension_name);
if (is_remote_install_) {
return l10n_util::GetStringFUTF16(
IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
base::UTF8ToUTF16(extension_name));
} else {
return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
base::UTF8ToUTF16(extension_name));
}
}
void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) {
ShowBubbleView(browser);
}
std::u16string ExtensionDisabledGlobalError::GetBubbleViewTitle() {
if (is_remote_install_) {
return l10n_util::GetStringFUTF16(
IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
base::UTF8ToUTF16(extension_->name()));
} else {
return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
base::UTF8ToUTF16(extension_->name()));
}
}
std::vector<std::u16string>
ExtensionDisabledGlobalError::GetBubbleViewMessages() {
std::vector<std::u16string> messages;
std::unique_ptr<const PermissionSet> granted_permissions =
ExtensionPrefs::Get(service_->GetBrowserContext())
->GetGrantedPermissions(extension_->id());
PermissionMessages permission_warnings =
extension_->permissions_data()->GetNewPermissionMessages(
*granted_permissions);
if (is_remote_install_) {
if (!permission_warnings.empty())
messages.push_back(
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO));
} else {
// TODO(crbug.com/461261): If NeedCustodianApprovalForPermissionIncrease,
// add an extra message for supervised users.
messages.push_back(
l10n_util::GetStringUTF16(IDS_EXTENSION_DISABLED_ERROR_LABEL));
}
for (const PermissionMessage& msg : permission_warnings) {
messages.push_back(l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE,
msg.message()));
}
return messages;
}
std::u16string ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
if (is_remote_install_) {
return l10n_util::GetStringUTF16(
extension_->is_app()
? IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_APP
: IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_EXTENSION);
}
return l10n_util::GetStringUTF16(
IDS_EXTENSION_PROMPT_PERMISSIONS_ACCEPT_BUTTON);
}
std::u16string ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON);
}
void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
// If the user takes an action, |user_response_| is set in
// BubbleView[Cancel|Accept]Pressed(). Otherwise, the IGNORE value set in the
// constructor is correct.
UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponseRemoteInstall2",
user_response_,
EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse2",
user_response_,
EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
// Reset in case the user does not follow through on subsequent dialogs to
// confirm removal decision, in which case the bubble can be shown again
// when the user clicks on the global error in the menu.
user_response_ = IGNORED;
}
void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
Browser* browser) {
user_response_ = REENABLE;
// Delay extension reenabling so this bubble closes properly.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ExtensionService::GrantPermissionsAndEnableExtension,
service_->AsWeakPtr(), base::RetainedRef(extension_)));
}
void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
Browser* browser) {
uninstall_dialog_ = ExtensionUninstallDialog::Create(
service_->profile(), browser->window()->GetNativeWindow(), this);
user_response_ = UNINSTALL;
// Delay showing the uninstall dialog, so that this function returns
// immediately, to close the bubble properly. See crbug.com/121544.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ExtensionUninstallDialog::ConfirmUninstall,
uninstall_dialog_->AsWeakPtr(),
base::RetainedRef(extension_),
UNINSTALL_REASON_EXTENSION_DISABLED,
UNINSTALL_SOURCE_PERMISSIONS_INCREASE));
}
bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
// Since this indicates that an extension was disabled, we should definitely
// have the user acknowledge it, rather than having the bubble disappear when
// a new window pops up.
return false;
}
bool ExtensionDisabledGlobalError::ShouldShowCloseButton() const {
// As we don't close the bubble on deactivation (see ShouldCloseOnDeactivate),
// we add a close button so the user doesn't *need* to act right away.
// If the bubble is closed, the error remains in the wrench menu and the user
// can address it later.
return true;
}
void ExtensionDisabledGlobalError::OnExtensionUninstallDialogClosed(
bool did_start_uninstall,
const std::u16string& error) {
// No need to do anything.
}
void ExtensionDisabledGlobalError::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
if (extension != extension_)
return;
RemoveGlobalError();
}
void ExtensionDisabledGlobalError::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const Extension* extension,
UninstallReason reason) {
if (extension != extension_)
return;
RemoveGlobalError();
}
void ExtensionDisabledGlobalError::OnShutdown(ExtensionRegistry* registry) {
DCHECK_EQ(ExtensionRegistry::Get(service_->profile()), registry);
registry_observation_.Reset();
}
void ExtensionDisabledGlobalError::RemoveGlobalError() {
std::unique_ptr<GlobalError> ptr =
GlobalErrorServiceFactory::GetForProfile(service_->profile())
->RemoveGlobalError(this);
registry_observation_.Reset();
// Delete this object after any running tasks, so that the extension dialog
// still has it as a delegate to finish the current tasks.
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, ptr.release());
}
// Globals --------------------------------------------------------------------
void AddExtensionDisabledError(ExtensionService* service,
const Extension* extension,
bool is_remote_install) {
if (extension) {
GlobalErrorServiceFactory::GetForProfile(service->profile())
->AddGlobalError(std::make_unique<ExtensionDisabledGlobalError>(
service, extension, is_remote_install));
}
}
} // namespace extensions