blob: 43472bee0b80adecfbecc98b92307dcfe4a79ac5 [file] [log] [blame]
[email protected]c82da8c42012-06-08 19:49:111// 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.
4
5#include "chrome/browser/extensions/extension_install_prompt.h"
6
7#include <map>
8
9#include "base/command_line.h"
10#include "base/file_util.h"
11#include "base/message_loop.h"
12#include "base/string_number_conversions.h"
13#include "base/string_util.h"
14#include "base/stringprintf.h"
15#include "base/utf_string_conversions.h"
16#include "chrome/browser/extensions/bundle_installer.h"
17#include "chrome/browser/extensions/extension_install_dialog.h"
18#include "chrome/browser/extensions/extension_install_ui.h"
19#include "chrome/browser/profiles/profile.h"
[email protected]b70a2d92012-06-28 19:51:2120#include "chrome/browser/signin/token_service.h"
21#include "chrome/browser/signin/token_service_factory.h"
[email protected]32fc4ff72012-06-15 21:50:0122#include "chrome/browser/ui/browser.h"
[email protected]c82da8c42012-06-08 19:49:1123#include "chrome/browser/ui/browser_navigator.h"
[email protected]b62084b2012-06-12 01:53:3024#include "chrome/browser/ui/tab_contents/tab_contents.h"
[email protected]c82da8c42012-06-08 19:49:1125#include "chrome/common/chrome_switches.h"
26#include "chrome/common/extensions/extension.h"
27#include "chrome/common/extensions/extension_icon_set.h"
28#include "chrome/common/extensions/extension_manifest_constants.h"
29#include "chrome/common/extensions/extension_resource.h"
30#include "chrome/common/extensions/extension_switch_utils.h"
31#include "chrome/common/extensions/url_pattern.h"
32#include "grit/chromium_strings.h"
33#include "grit/generated_resources.h"
[email protected]d7a8ef62012-06-22 16:28:4634#include "grit/theme_resources_standard.h"
[email protected]c82da8c42012-06-08 19:49:1135#include "ui/base/l10n/l10n_util.h"
36#include "ui/base/resource/resource_bundle.h"
37#include "ui/gfx/image/image.h"
38
39#if defined(USE_ASH)
40#include "ash/shell.h"
41#endif
42
43using extensions::BundleInstaller;
44using extensions::Extension;
[email protected]c2e66e12012-06-27 06:27:0645using extensions::PermissionSet;
[email protected]c82da8c42012-06-08 19:49:1146
47static const int kTitleIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
48 0, // The regular install prompt depends on what's being installed.
49 IDS_EXTENSION_INLINE_INSTALL_PROMPT_TITLE,
50 IDS_EXTENSION_INSTALL_PROMPT_TITLE,
51 IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE,
52 IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE
53};
54static const int kHeadingIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
55 IDS_EXTENSION_INSTALL_PROMPT_HEADING,
56 0, // Inline installs use the extension name.
57 0, // Heading for bundle installs depends on the bundle contents.
58 IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING,
59 IDS_EXTENSION_PERMISSIONS_PROMPT_HEADING
60};
61static const int kAcceptButtonIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
62 IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
63 IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
64 IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
65 IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON,
66 IDS_EXTENSION_PROMPT_PERMISSIONS_BUTTON
67};
68static const int kAbortButtonIds[ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
69 0, // These all use the platform's default cancel label.
70 0,
71 0,
72 0,
73 IDS_EXTENSION_PROMPT_PERMISSIONS_ABORT_BUTTON
74};
75static const int kPermissionsHeaderIds[
76 ExtensionInstallPrompt::NUM_PROMPT_TYPES] = {
77 IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
78 IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
79 IDS_EXTENSION_PROMPT_THESE_WILL_HAVE_ACCESS_TO,
80 IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO,
81 IDS_EXTENSION_PROMPT_WANTS_ACCESS_TO,
82};
83
84namespace {
85
86// Size of extension icon in top left of dialog.
87const int kIconSize = 69;
88
89} // namespace
90
91ExtensionInstallPrompt::Prompt::Prompt(PromptType type)
92 : type_(type),
93 extension_(NULL),
94 bundle_(NULL),
95 average_rating_(0.0),
96 rating_count_(0) {
97}
98
99ExtensionInstallPrompt::Prompt::~Prompt() {
100}
101
102void ExtensionInstallPrompt::Prompt::SetPermissions(
103 const std::vector<string16>& permissions) {
104 permissions_ = permissions;
105}
106
[email protected]b70a2d92012-06-28 19:51:21107void ExtensionInstallPrompt::Prompt::SetOAuthIssueAdvice(
108 const IssueAdviceInfo& issue_advice) {
109 oauth_issue_advice_ = issue_advice;
110}
111
[email protected]c82da8c42012-06-08 19:49:11112void ExtensionInstallPrompt::Prompt::SetInlineInstallWebstoreData(
113 const std::string& localized_user_count,
114 double average_rating,
115 int rating_count) {
116 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
117 localized_user_count_ = localized_user_count;
118 average_rating_ = average_rating;
119 rating_count_ = rating_count;
120}
121
122string16 ExtensionInstallPrompt::Prompt::GetDialogTitle() const {
123
124 int resource_id = kTitleIds[type_];
125
126 if (type_ == INSTALL_PROMPT) {
127 if (extension_->is_app())
128 resource_id = IDS_EXTENSION_INSTALL_APP_PROMPT_TITLE;
129 else if (extension_->is_theme())
130 resource_id = IDS_EXTENSION_INSTALL_THEME_PROMPT_TITLE;
131 else
132 resource_id = IDS_EXTENSION_INSTALL_EXTENSION_PROMPT_TITLE;
133 }
134
135 return l10n_util::GetStringUTF16(resource_id);
136}
137
138string16 ExtensionInstallPrompt::Prompt::GetHeading() const {
139 if (type_ == INLINE_INSTALL_PROMPT) {
140 return UTF8ToUTF16(extension_->name());
141 } else if (type_ == BUNDLE_INSTALL_PROMPT) {
142 return bundle_->GetHeadingTextFor(BundleInstaller::Item::STATE_PENDING);
143 } else {
144 return l10n_util::GetStringFUTF16(
145 kHeadingIds[type_], UTF8ToUTF16(extension_->name()));
146 }
147}
148
149string16 ExtensionInstallPrompt::Prompt::GetAcceptButtonLabel() const {
150 return l10n_util::GetStringUTF16(kAcceptButtonIds[type_]);
151}
152
153bool ExtensionInstallPrompt::Prompt::HasAbortButtonLabel() const {
154 return kAbortButtonIds[type_] > 0;
155}
156
157string16 ExtensionInstallPrompt::Prompt::GetAbortButtonLabel() const {
158 CHECK(HasAbortButtonLabel());
159 return l10n_util::GetStringUTF16(kAbortButtonIds[type_]);
160}
161
162string16 ExtensionInstallPrompt::Prompt::GetPermissionsHeading() const {
163 return l10n_util::GetStringUTF16(kPermissionsHeaderIds[type_]);
164}
165
166void ExtensionInstallPrompt::Prompt::AppendRatingStars(
167 StarAppender appender, void* data) const {
168 CHECK(appender);
169 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
170 int rating_integer = floor(average_rating_);
171 double rating_fractional = average_rating_ - rating_integer;
172
173 if (rating_fractional > 0.66) {
174 rating_integer++;
175 }
176
177 if (rating_fractional < 0.33 || rating_fractional > 0.66) {
178 rating_fractional = 0;
179 }
180
181 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
182 int i;
183 for (i = 0; i < rating_integer; i++) {
184 appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_ON), data);
185 }
186 if (rating_fractional) {
187 appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_HALF_LEFT), data);
188 i++;
189 }
190 for (; i < kMaxExtensionRating; i++) {
191 appender(rb.GetImageSkiaNamed(IDR_EXTENSIONS_RATING_STAR_OFF), data);
192 }
193}
194
195string16 ExtensionInstallPrompt::Prompt::GetRatingCount() const {
196 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
197 return l10n_util::GetStringFUTF16(
198 IDS_EXTENSION_RATING_COUNT,
199 UTF8ToUTF16(base::IntToString(rating_count_)));
200}
201
202string16 ExtensionInstallPrompt::Prompt::GetUserCount() const {
203 CHECK_EQ(INLINE_INSTALL_PROMPT, type_);
204 return l10n_util::GetStringFUTF16(
205 IDS_EXTENSION_USER_COUNT,
206 UTF8ToUTF16(localized_user_count_));
207}
208
209size_t ExtensionInstallPrompt::Prompt::GetPermissionCount() const {
210 return permissions_.size();
211}
212
213string16 ExtensionInstallPrompt::Prompt::GetPermission(size_t index) const {
214 CHECK_LT(index, permissions_.size());
215 return l10n_util::GetStringFUTF16(
216 IDS_EXTENSION_PERMISSION_LINE, permissions_[index]);
217}
218
[email protected]b70a2d92012-06-28 19:51:21219size_t ExtensionInstallPrompt::Prompt::GetOAuthIssueCount() const {
220 return oauth_issue_advice_.size();
221}
222
223const IssueAdviceInfoEntry& ExtensionInstallPrompt::Prompt::GetOAuthIssue(
224 size_t index) const {
225 CHECK_LT(index, oauth_issue_advice_.size());
226 return oauth_issue_advice_[index];
227}
228
[email protected]c82da8c42012-06-08 19:49:11229// static
230scoped_refptr<Extension>
231 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
232 const DictionaryValue* manifest,
233 const std::string& id,
234 const std::string& localized_name,
235 const std::string& localized_description,
236 std::string* error) {
237 scoped_ptr<DictionaryValue> localized_manifest;
238 if (!localized_name.empty() || !localized_description.empty()) {
239 localized_manifest.reset(manifest->DeepCopy());
240 if (!localized_name.empty()) {
241 localized_manifest->SetString(extension_manifest_keys::kName,
242 localized_name);
243 }
244 if (!localized_description.empty()) {
245 localized_manifest->SetString(extension_manifest_keys::kDescription,
246 localized_description);
247 }
248 }
249
250 return Extension::Create(
251 FilePath(),
252 Extension::INTERNAL,
253 localized_manifest.get() ? *localized_manifest.get() : *manifest,
254 Extension::NO_FLAGS,
255 id,
256 error);
257}
258
[email protected]32fc4ff72012-06-15 21:50:01259ExtensionInstallPrompt::ExtensionInstallPrompt(Browser* browser)
[email protected]b70a2d92012-06-28 19:51:21260 : record_oauth2_grant_(ShouldAutomaticallyApproveScopes()),
261 browser_(browser),
[email protected]c82da8c42012-06-08 19:49:11262 ui_loop_(MessageLoop::current()),
263 extension_(NULL),
[email protected]32fc4ff72012-06-15 21:50:01264 install_ui_(ExtensionInstallUI::Create(browser)),
[email protected]c82da8c42012-06-08 19:49:11265 delegate_(NULL),
266 prompt_(UNSET_PROMPT_TYPE),
267 prompt_type_(UNSET_PROMPT_TYPE),
268 ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) {
269}
270
271ExtensionInstallPrompt::~ExtensionInstallPrompt() {
272}
273
274void ExtensionInstallPrompt::ConfirmBundleInstall(
275 extensions::BundleInstaller* bundle,
[email protected]c2e66e12012-06-27 06:27:06276 const PermissionSet* permissions) {
[email protected]c82da8c42012-06-08 19:49:11277 DCHECK(ui_loop_ == MessageLoop::current());
278 bundle_ = bundle;
279 permissions_ = permissions;
280 delegate_ = bundle;
281 prompt_type_ = BUNDLE_INSTALL_PROMPT;
282
[email protected]b70a2d92012-06-28 19:51:21283 FetchOAuthIssueAdviceIfNeeded();
[email protected]c82da8c42012-06-08 19:49:11284}
285
286void ExtensionInstallPrompt::ConfirmInlineInstall(
287 Delegate* delegate,
288 const Extension* extension,
289 SkBitmap* icon,
290 const ExtensionInstallPrompt::Prompt& prompt) {
291 DCHECK(ui_loop_ == MessageLoop::current());
292 extension_ = extension;
293 permissions_ = extension->GetActivePermissions();
294 delegate_ = delegate;
295 prompt_ = prompt;
296 prompt_type_ = INLINE_INSTALL_PROMPT;
297
298 SetIcon(icon);
[email protected]b70a2d92012-06-28 19:51:21299 FetchOAuthIssueAdviceIfNeeded();
[email protected]c82da8c42012-06-08 19:49:11300}
301
302void ExtensionInstallPrompt::ConfirmWebstoreInstall(Delegate* delegate,
303 const Extension* extension,
304 const SkBitmap* icon) {
305 // SetIcon requires |extension_| to be set. ConfirmInstall will setup the
306 // remaining fields.
307 extension_ = extension;
308 SetIcon(icon);
309 ConfirmInstall(delegate, extension);
310}
311
312void ExtensionInstallPrompt::ConfirmInstall(Delegate* delegate,
313 const Extension* extension) {
314 DCHECK(ui_loop_ == MessageLoop::current());
315 extension_ = extension;
316 permissions_ = extension->GetActivePermissions();
317 delegate_ = delegate;
318 prompt_type_ = INSTALL_PROMPT;
319
320 // We special-case themes to not show any confirm UI. Instead they are
321 // immediately installed, and then we show an infobar (see OnInstallSuccess)
322 // to allow the user to revert if they don't like it.
323 //
324 // We don't do this in the case where off-store extension installs are
325 // disabled because in that case, we don't show the dangerous download UI, so
326 // we need the UI confirmation.
327 if (extension->is_theme()) {
328 if (extension->from_webstore() ||
329 extensions::switch_utils::IsEasyOffStoreInstallEnabled()) {
330 delegate->InstallUIProceed();
331 return;
332 }
333 }
334
335 LoadImageIfNeeded();
336}
337
338void ExtensionInstallPrompt::ConfirmReEnable(Delegate* delegate,
339 const Extension* extension) {
340 DCHECK(ui_loop_ == MessageLoop::current());
341 extension_ = extension;
342 permissions_ = extension->GetActivePermissions();
343 delegate_ = delegate;
344 prompt_type_ = RE_ENABLE_PROMPT;
345
346 LoadImageIfNeeded();
347}
348
349void ExtensionInstallPrompt::ConfirmPermissions(
350 Delegate* delegate,
351 const Extension* extension,
[email protected]c2e66e12012-06-27 06:27:06352 const PermissionSet* permissions) {
[email protected]c82da8c42012-06-08 19:49:11353 DCHECK(ui_loop_ == MessageLoop::current());
354 extension_ = extension;
355 permissions_ = permissions;
356 delegate_ = delegate;
357 prompt_type_ = PERMISSIONS_PROMPT;
358
359 LoadImageIfNeeded();
360}
361
362void ExtensionInstallPrompt::OnInstallSuccess(const Extension* extension,
363 SkBitmap* icon) {
364 extension_ = extension;
365 SetIcon(icon);
366
367 install_ui_->OnInstallSuccess(extension, &icon_);
368}
369
[email protected]4eaf0b32012-06-19 06:33:28370void ExtensionInstallPrompt::OnInstallFailure(const CrxInstallerError& error) {
[email protected]c82da8c42012-06-08 19:49:11371 install_ui_->OnInstallFailure(error);
372}
373
374void ExtensionInstallPrompt::SetIcon(const SkBitmap* image) {
375 if (image)
376 icon_ = *image;
377 else
378 icon_ = SkBitmap();
379 if (icon_.empty())
380 icon_ = Extension::GetDefaultIcon(extension_->is_app());
381}
382
383void ExtensionInstallPrompt::OnImageLoaded(const gfx::Image& image,
384 const std::string& extension_id,
385 int index) {
386 SetIcon(image.IsEmpty() ? NULL : image.ToSkBitmap());
[email protected]b70a2d92012-06-28 19:51:21387 FetchOAuthIssueAdviceIfNeeded();
[email protected]c82da8c42012-06-08 19:49:11388}
389
390void ExtensionInstallPrompt::LoadImageIfNeeded() {
391 // Bundle install prompts do not have an icon.
392 if (!icon_.empty()) {
[email protected]b70a2d92012-06-28 19:51:21393 FetchOAuthIssueAdviceIfNeeded();
[email protected]c82da8c42012-06-08 19:49:11394 return;
395 }
396
397 // Load the image asynchronously. For the response, check OnImageLoaded.
398 ExtensionResource image =
399 extension_->GetIconResource(ExtensionIconSet::EXTENSION_ICON_LARGE,
400 ExtensionIconSet::MATCH_BIGGER);
401 tracker_.LoadImage(extension_, image,
402 gfx::Size(kIconSize, kIconSize),
403 ImageLoadingTracker::DONT_CACHE);
404}
405
[email protected]b70a2d92012-06-28 19:51:21406void ExtensionInstallPrompt::FetchOAuthIssueAdviceIfNeeded() {
407 const Extension::OAuth2Info& oauth2_info = extension_->oauth2_info();
408 if (ShouldAutomaticallyApproveScopes() ||
409 oauth2_info.client_id.empty() ||
410 permissions_->scopes().empty() ||
411 prompt_type_ == BUNDLE_INSTALL_PROMPT ||
412 prompt_type_ == INLINE_INSTALL_PROMPT) {
413 ShowConfirmation();
414 return;
415 }
416
417 Profile* profile = install_ui_->browser()->profile();
418 TokenService* token_service = TokenServiceFactory::GetForProfile(profile);
419 std::vector<std::string> scopes;
420 scopes.assign(permissions_->scopes().begin(), permissions_->scopes().end());
421
422 token_flow_.reset(new OAuth2MintTokenFlow(
423 profile->GetRequestContext(),
424 this,
425 OAuth2MintTokenFlow::Parameters(
426 token_service->GetOAuth2LoginRefreshToken(),
427 extension_->id(),
428 oauth2_info.client_id,
429 scopes,
430 OAuth2MintTokenFlow::MODE_ISSUE_ADVICE)));
431 token_flow_->Start();
432}
433
434void ExtensionInstallPrompt::OnIssueAdviceSuccess(
435 const IssueAdviceInfo& advice_info) {
436 prompt_.SetOAuthIssueAdvice(advice_info);
437 record_oauth2_grant_ = true;
438 ShowConfirmation();
439}
440
441void ExtensionInstallPrompt::OnMintTokenFailure(
442 const GoogleServiceAuthError& error) {
443 ShowConfirmation();
444}
445
[email protected]c82da8c42012-06-08 19:49:11446void ExtensionInstallPrompt::ShowConfirmation() {
447 prompt_.set_type(prompt_type_);
448 prompt_.SetPermissions(permissions_->GetWarningMessages());
449
450 switch (prompt_type_) {
451 case PERMISSIONS_PROMPT:
452 case RE_ENABLE_PROMPT:
453 case INLINE_INSTALL_PROMPT:
454 case INSTALL_PROMPT: {
455 prompt_.set_extension(extension_);
456 prompt_.set_icon(gfx::Image(icon_));
[email protected]32fc4ff72012-06-15 21:50:01457 ShowExtensionInstallDialog(browser_, delegate_, prompt_);
[email protected]c82da8c42012-06-08 19:49:11458 break;
459 }
460 case BUNDLE_INSTALL_PROMPT: {
461 prompt_.set_bundle(bundle_);
[email protected]32fc4ff72012-06-15 21:50:01462 ShowExtensionInstallDialog(browser_, delegate_, prompt_);
[email protected]c82da8c42012-06-08 19:49:11463 break;
464 }
465 default:
466 NOTREACHED() << "Unknown message";
467 break;
468 }
469}
[email protected]b70a2d92012-06-28 19:51:21470
471// static
472bool ExtensionInstallPrompt::ShouldAutomaticallyApproveScopes() {
473 return !CommandLine::ForCurrentProcess()->HasSwitch(
474 switches::kDemandUserScopeApproval);
475}