blob: 2113b174e8bc2b98e19a8e778bfe4adee3d5044c [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_install_prompt.h"
#include <map>
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/extensions/bundle_installer.h"
#include "chrome/browser/extensions/extension_install_dialog.h"
#include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/token_service.h"
#include "chrome/browser/signin/token_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/extension_resource.h"
#include "chrome/common/extensions/extension_switch_utils.h"
#include "chrome/common/extensions/permissions/permission_set.h"
#include "chrome/common/extensions/url_pattern.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/page_navigator.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
using extensions::BundleInstaller;
using extensions::Extension;
using extensions::PermissionSet;
static const int kTitleIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
0, // The regular install prompt depends on what's being installed.
IDS_EXTENSION_INLINE_INSTALL_PROMPT_TITLE,
IDS_EXTENSION_INSTALL_PROMPT_TITLE,
IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE,
IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE
};
static const int kHeadingIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
IDS_EXTENSION_INSTALL_PROMPT_HEADING,
0, // Inline installs use the extension name.
0, // Heading for bundle installs depends on the bundle contents.
IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING,
IDS_EXTENSION_PERMISSIONS_PROMPT_HEADING
};
static const int kAcceptButtonIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON,
IDS_EXTENSION_PROMPT_PERMISSIONS_BUTTON
};
static const int kAbortButtonIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
0, // These all use the platform's default cancel label.
0,
0,
0,
IDS_EXTENSION_PROMPT_PERMISSIONS_ABORT_BUTTON
};
static const int kPermissionsHeaderIds[
ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
IDS_EXTENSION_PROMPT_THESE_WILL_HAVE_ACCESS_TO,
IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO,
IDS_EXTENSION_PROMPT_WANTS_ACCESS_TO,
};
static const int kOAuthHeaderIds[
ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
IDS_EXTENSION_PROMPT_OAUTH_HEADER,
0, // Inline installs don't show OAuth permissions.
0, // Bundle installs don't show OAuth permissions.
IDS_EXTENSION_PROMPT_OAUTH_REENABLE_HEADER,
IDS_EXTENSION_PROMPT_OAUTH_PERMISSIONS_HEADER,
};
namespace {
// Size of extension icon in top left of dialog.
const int kIconSize = 69;
// Returns pixel size under maximal scale factor for the icon whose device
// independent size is |size_in_dip|
int GetSizeForMaxScaleFactor(int size_in_dip) {
std::vector<ui::ScaleFactor> supported_scale_factors =
ui::GetSupportedScaleFactors();
// Scale factors are in ascending order, so the last one is the one we need.
ui::ScaleFactor max_scale_factor = supported_scale_factors.back();
float max_scale_factor_scale = ui::GetScaleFactorScale(max_scale_factor);
return static_cast<int>(size_in_dip * max_scale_factor_scale);
}
// Returns bitmap for the default icon with size equal to the default icon's
// pixel size under maximal supported scale factor.
SkBitmap GetDefaultIconBitmapForMaxScaleFactor(bool is_app) {
std::vector<ui::ScaleFactor> supported_scale_factors =
ui::GetSupportedScaleFactors();
// Scale factors are in ascending order, so the last one is the one we need.
ui::ScaleFactor max_scale_factor =
supported_scale_factors[supported_scale_factors.size() - 1];
return Extension::GetDefaultIcon(is_app).
GetRepresentation(max_scale_factor).sk_bitmap();
}
} // namespace
ExtensionInstallPrompt::Prompt::Prompt(Profile* profile, PromptType type)
: type_(type),
extension_(NULL),
bundle_(NULL),
average_rating_(0.0),
rating_count_(0),
profile_(profile) {
}
ExtensionInstallPrompt::Prompt::~Prompt() {
}
void ExtensionInstallPrompt::Prompt::SetPermissions(
const std::vector<string16>& permissions) {
permissions_ = permissions;
}
void ExtensionInstallPrompt::Prompt::SetOAuthIssueAdvice(
const IssueAdviceInfo& issue_advice) {
oauth_issue_advice_ = issue_advice;
}
void ExtensionInstallPrompt::Prompt::SetInlineInstallWebstoreData(
const std::string& localized_user_count,
double average_rating,
int rating_count) {
CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
localized_user_count_ = localized_user_count;
average_rating_ = average_rating;
rating_count_ = rating_count;
}
string16 ExtensionInstallPrompt::Prompt::GetDialogTitle() const {
int resource_id = kTitleIds[type_];
if (type_ == INSTALL_PROMPT) {
if (extension_->is_app())
resource_id = IDS_EXTENSION_INSTALL_APP_PROMPT_TITLE;
else if (extension_->is_theme())
resource_id = IDS_EXTENSION_INSTALL_THEME_PROMPT_TITLE;
else
resource_id = IDS_EXTENSION_INSTALL_EXTENSION_PROMPT_TITLE;
}
return l10n_util::GetStringUTF16(resource_id);
}
string16 ExtensionInstallPrompt::Prompt::GetHeading() const {
if (type_ == INLINE_INSTALL_PROMPT) {
return UTF8ToUTF16(extension_->name());
} else if (type_ == BUNDLE_INSTALL_PROMPT) {
return bundle_->GetHeadingTextFor(BundleInstaller::Item::STATE_PENDING);
} else {
return l10n_util::GetStringFUTF16(
kHeadingIds[type_], UTF8ToUTF16(extension_->name()));
}
}
string16 ExtensionInstallPrompt::Prompt::GetAcceptButtonLabel() const {
return l10n_util::GetStringUTF16(kAcceptButtonIds[type_]);
}
bool ExtensionInstallPrompt::Prompt::HasAbortButtonLabel() const {
return kAbortButtonIds[type_] > 0;
}
string16 ExtensionInstallPrompt::Prompt::GetAbortButtonLabel() const {
CHECK(HasAbortButtonLabel());
return l10n_util::GetStringUTF16(kAbortButtonIds[type_]);
}
string16 ExtensionInstallPrompt::Prompt::GetPermissionsHeading() const {
return l10n_util::GetStringUTF16(kPermissionsHeaderIds[type_]);
}
string16 ExtensionInstallPrompt::Prompt::GetOAuthHeading() const {
string16 username(ASCIIToUTF16("username@example.com"));
// |profile_| can be NULL in unit tests.
if (profile_) {
username = UTF8ToUTF16(profile_->GetPrefs()->GetString(
prefs::kGoogleServicesUsername));
}
int resource_id = kOAuthHeaderIds[type_];
return l10n_util::GetStringFUTF16(resource_id, username);
}
void ExtensionInstallPrompt::Prompt::AppendRatingStars(
StarAppender appender, void* data) const {
CHECK(appender);
CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
int rating_integer = floor(average_rating_);
double rating_fractional = average_rating_ - rating_integer;
if (rating_fractional > 0.66) {
rating_integer++;
}
if (rating_fractional < 0.33 || rating_fractional > 0.66) {
rating_fractional = 0;
}
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
int i;
for (i = 0; i < rating_integer; i++) {
appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_ON), data);
}
if (rating_fractional) {
appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_HALF_LEFT), data);
i++;
}
for (; i < kMaxExtensionRating; i++) {
appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_OFF), data);
}
}
string16 ExtensionInstallPrompt::Prompt::GetRatingCount() const {
CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
return l10n_util::GetStringFUTF16(
IDS_EXTENSION_RATING_COUNT,
UTF8ToUTF16(base::IntToString(rating_count_)));
}
string16 ExtensionInstallPrompt::Prompt::GetUserCount() const {
CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
return l10n_util::GetStringFUTF16(
IDS_EXTENSION_USER_COUNT,
UTF8ToUTF16(localized_user_count_));
}
size_t ExtensionInstallPrompt::Prompt::GetPermissionCount() const {
return permissions_.size();
}
string16 ExtensionInstallPrompt::Prompt::GetPermission(size_t index) const {
CHECK_LT(index, permissions_.size());
return permissions_[index];
}
size_t ExtensionInstallPrompt::Prompt::GetOAuthIssueCount() const {
return oauth_issue_advice_.size();
}
const IssueAdviceInfoEntry& ExtensionInstallPrompt::Prompt::GetOAuthIssue(
size_t index) const {
CHECK_LT(index, oauth_issue_advice_.size());
return oauth_issue_advice_[index];
}
// static
scoped_refptr<Extension>
ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
const DictionaryValue* manifest,
int flags,
const std::string& id,
const std::string& localized_name,
const std::string& localized_description,
std::string* error) {
scoped_ptr<DictionaryValue> localized_manifest;
if (!localized_name.empty() || !localized_description.empty()) {
localized_manifest.reset(manifest->DeepCopy());
if (!localized_name.empty()) {
localized_manifest->SetString(extension_manifest_keys::kName,
localized_name);
}
if (!localized_description.empty()) {
localized_manifest->SetString(extension_manifest_keys::kDescription,
localized_description);
}
}
return Extension::Create(
FilePath(),
Extension::INTERNAL,
localized_manifest.get() ? *localized_manifest.get() : *manifest,
flags,
id,
error);
}
ExtensionInstallPrompt::ExtensionInstallPrompt(
gfx::NativeWindow parent,
content::PageNavigator* navigator,
Profile* profile)
: record_oauth2_grant_(false),
parent_(parent),
navigator_(navigator),
ui_loop_(MessageLoop::current()),
extension_(NULL),
install_ui_(ExtensionInstallUI::Create(profile)),
delegate_(NULL),
prompt_(profile, UNSET_PROMPT_TYPE),
prompt_type_(UNSET_PROMPT_TYPE),
ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) {
}
ExtensionInstallPrompt::~ExtensionInstallPrompt() {
}
void ExtensionInstallPrompt::ConfirmBundleInstall(
extensions::BundleInstaller* bundle,
const PermissionSet* permissions) {
DCHECK(ui_loop_ == MessageLoop::current());
bundle_ = bundle;
permissions_ = permissions;
delegate_ = bundle;
prompt_type_ = BUNDLE_INSTALL_PROMPT;
FetchOAuthIssueAdviceIfNeeded();
}
void ExtensionInstallPrompt::ConfirmInlineInstall(
Delegate* delegate,
const Extension* extension,
SkBitmap* icon,
const ExtensionInstallPrompt::Prompt& prompt) {
DCHECK(ui_loop_ == MessageLoop::current());
extension_ = extension;
permissions_ = extension->GetActivePermissions();
delegate_ = delegate;
prompt_ = prompt;
prompt_type_ = INLINE_INSTALL_PROMPT;
SetIcon(icon);
FetchOAuthIssueAdviceIfNeeded();
}
void ExtensionInstallPrompt::ConfirmWebstoreInstall(Delegate* delegate,
const Extension* extension,
const SkBitmap* icon) {
// SetIcon requires |extension_| to be set. ConfirmInstall will setup the
// remaining fields.
extension_ = extension;
SetIcon(icon);
ConfirmInstall(delegate, extension);
}
void ExtensionInstallPrompt::ConfirmInstall(Delegate* delegate,
const Extension* extension) {
DCHECK(ui_loop_ == MessageLoop::current());
extension_ = extension;
permissions_ = extension->GetActivePermissions();
delegate_ = delegate;
prompt_type_ = INSTALL_PROMPT;
// We special-case themes to not show any confirm UI. Instead they are
// immediately installed, and then we show an infobar (see OnInstallSuccess)
// to allow the user to revert if they don't like it.
//
// We don't do this in the case where off-store extension installs are
// disabled because in that case, we don't show the dangerous download UI, so
// we need the UI confirmation.
if (extension->is_theme()) {
if (extension->from_webstore() ||
extensions::switch_utils::IsEasyOffStoreInstallEnabled()) {
delegate->InstallUIProceed();
return;
}
}
LoadImageIfNeeded();
}
void ExtensionInstallPrompt::ConfirmReEnable(Delegate* delegate,
const Extension* extension) {
DCHECK(ui_loop_ == MessageLoop::current());
extension_ = extension;
permissions_ = extension->GetActivePermissions();
delegate_ = delegate;
prompt_type_ = RE_ENABLE_PROMPT;
LoadImageIfNeeded();
}
void ExtensionInstallPrompt::ConfirmPermissions(
Delegate* delegate,
const Extension* extension,
const PermissionSet* permissions) {
DCHECK(ui_loop_ == MessageLoop::current());
extension_ = extension;
permissions_ = permissions;
delegate_ = delegate;
prompt_type_ = PERMISSIONS_PROMPT;
LoadImageIfNeeded();
}
void ExtensionInstallPrompt::ConfirmIssueAdvice(
Delegate* delegate,
const Extension* extension,
const IssueAdviceInfo& issue_advice) {
DCHECK(ui_loop_ == MessageLoop::current());
extension_ = extension;
delegate_ = delegate;
prompt_type_ = PERMISSIONS_PROMPT;
record_oauth2_grant_ = true;
prompt_.SetOAuthIssueAdvice(issue_advice);
LoadImageIfNeeded();
}
void ExtensionInstallPrompt::OnInstallSuccess(const Extension* extension,
SkBitmap* icon) {
extension_ = extension;
SetIcon(icon);
install_ui_->OnInstallSuccess(extension, &icon_);
}
void ExtensionInstallPrompt::OnInstallFailure(
const extensions::CrxInstallerError& error) {
install_ui_->OnInstallFailure(error);
}
void ExtensionInstallPrompt::SetIcon(const SkBitmap* image) {
if (image)
icon_ = *image;
else
icon_ = SkBitmap();
if (icon_.empty()) {
// Let's set default icon bitmap whose size is equal to the default icon's
// pixel size under maximal supported scale factor. If the bitmap is larger
// than the one we need, it will be scaled down by the ui code.
icon_ = GetDefaultIconBitmapForMaxScaleFactor(extension_->is_app());
}
}
void ExtensionInstallPrompt::OnImageLoaded(const gfx::Image& image,
const std::string& extension_id,
int index) {
SetIcon(image.IsEmpty() ? NULL : image.ToSkBitmap());
FetchOAuthIssueAdviceIfNeeded();
}
void ExtensionInstallPrompt::LoadImageIfNeeded() {
// Bundle install prompts do not have an icon.
if (!icon_.empty()) {
FetchOAuthIssueAdviceIfNeeded();
return;
}
// Load the image asynchronously. For the response, check OnImageLoaded.
ExtensionResource image =
extension_->GetIconResource(extension_misc::EXTENSION_ICON_LARGE,
ExtensionIconSet::MATCH_BIGGER);
// Load the icon whose pixel size is large enough to be displayed under
// maximal supported scale factor. UI code will scale the icon down if needed.
// TODO(tbarzic): We should use IconImage here and load the required bitmap
// lazily.
int pixel_size = GetSizeForMaxScaleFactor(kIconSize);
tracker_.LoadImage(extension_, image,
gfx::Size(pixel_size, pixel_size),
ImageLoadingTracker::DONT_CACHE);
}
void ExtensionInstallPrompt::FetchOAuthIssueAdviceIfNeeded() {
// |extension_| may be NULL, e.g. in the bundle install case.
if (!extension_ ||
prompt_type_ == BUNDLE_INSTALL_PROMPT ||
prompt_type_ == INLINE_INSTALL_PROMPT ||
prompt_.GetOAuthIssueCount() != 0U) {
ShowConfirmation();
return;
}
const Extension::OAuth2Info& oauth2_info = extension_->oauth2_info();
if (oauth2_info.client_id.empty() ||
oauth2_info.scopes.empty()) {
ShowConfirmation();
return;
}
Profile* profile = install_ui_->profile();
TokenService* token_service = TokenServiceFactory::GetForProfile(profile);
token_flow_.reset(new OAuth2MintTokenFlow(
profile->GetRequestContext(),
this,
OAuth2MintTokenFlow::Parameters(
token_service->GetOAuth2LoginRefreshToken(),
extension_->id(),
oauth2_info.client_id,
oauth2_info.scopes,
OAuth2MintTokenFlow::MODE_ISSUE_ADVICE)));
token_flow_->Start();
}
void ExtensionInstallPrompt::OnIssueAdviceSuccess(
const IssueAdviceInfo& advice_info) {
prompt_.SetOAuthIssueAdvice(advice_info);
record_oauth2_grant_ = true;
ShowConfirmation();
}
void ExtensionInstallPrompt::OnMintTokenFailure(
const GoogleServiceAuthError& error) {
ShowConfirmation();
}
void ExtensionInstallPrompt::ShowConfirmation() {
prompt_.set_type(prompt_type_);
if (permissions_) {
Extension::Type extension_type = prompt_type_ == BUNDLE_INSTALL_PROMPT ?
Extension::TYPE_UNKNOWN : extension_->GetType();
prompt_.SetPermissions(permissions_->GetWarningMessages(extension_type));
}
switch (prompt_type_) {
case PERMISSIONS_PROMPT:
case RE_ENABLE_PROMPT:
case INLINE_INSTALL_PROMPT:
case INSTALL_PROMPT: {
prompt_.set_extension(extension_);
prompt_.set_icon(gfx::Image(icon_));
ShowExtensionInstallDialog(parent_, navigator_, delegate_, prompt_);
break;
}
case BUNDLE_INSTALL_PROMPT: {
prompt_.set_bundle(bundle_);
ShowExtensionInstallDialog(parent_, navigator_, delegate_, prompt_);
break;
}
default:
NOTREACHED() << "Unknown message";
break;
}
}
namespace chrome {
ExtensionInstallPrompt* CreateExtensionInstallPromptWithBrowser(
Browser* browser) {
// |browser| can be NULL in unit tests.
if (!browser)
return new ExtensionInstallPrompt(NULL, NULL, NULL);
gfx::NativeWindow parent =
browser->window() ? browser->window()->GetNativeWindow() : NULL;
return new ExtensionInstallPrompt(parent, browser, browser->profile());
}
} // namespace chrome