blob: 1a493fa6869686cfedf3916be78c6408b552a25c [file] [log] [blame]
[email protected]2894a512014-06-26 19:03:561// Copyright 2014 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/external_install_error.h"
6
avia2f4804a2015-12-24 23:11:137#include <stddef.h>
dcheng1fc00f12015-12-26 22:18:038#include <utility>
avia2f4804a2015-12-24 23:11:139
[email protected]2894a512014-06-26 19:03:5610#include "base/bind.h"
avia2f4804a2015-12-24 23:11:1311#include "base/macros.h"
rdevlin.croninb2daf2e42016-01-14 20:00:5412#include "base/message_loop/message_loop.h"
13#include "base/metrics/histogram_macros.h"
[email protected]2894a512014-06-26 19:03:5614#include "base/strings/utf_string_conversions.h"
15#include "chrome/app/chrome_command_ids.h"
lazyboy0b9b30f2016-01-05 03:15:3716#include "chrome/browser/extensions/extension_install_error_menu_item_id_provider.h"
pkotwicz2f181782014-10-29 17:33:4517#include "chrome/browser/extensions/extension_install_prompt_show_params.h"
[email protected]2894a512014-06-26 19:03:5618#include "chrome/browser/extensions/extension_service.h"
19#include "chrome/browser/extensions/external_install_manager.h"
20#include "chrome/browser/extensions/webstore_data_fetcher.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_finder.h"
24#include "chrome/browser/ui/global_error/global_error.h"
25#include "chrome/browser/ui/global_error/global_error_service.h"
26#include "chrome/browser/ui/global_error/global_error_service_factory.h"
27#include "chrome/browser/ui/tabs/tab_strip_model.h"
[email protected]af39f002014-08-22 10:18:1828#include "chrome/grit/generated_resources.h"
[email protected]2894a512014-06-26 19:03:5629#include "extensions/browser/extension_registry.h"
30#include "extensions/browser/extension_system.h"
[email protected]e43c61f2014-07-20 21:46:3431#include "extensions/browser/uninstall_reason.h"
[email protected]2894a512014-06-26 19:03:5632#include "extensions/common/constants.h"
33#include "extensions/common/extension.h"
[email protected]2894a512014-06-26 19:03:5634#include "ui/base/l10n/l10n_util.h"
35#include "ui/gfx/image/image.h"
rdevlin.cronin3fe4bd32016-01-12 18:45:4036#include "ui/gfx/image/image_skia.h"
[email protected]2894a512014-06-26 19:03:5637#include "ui/gfx/image/image_skia_operations.h"
38
39namespace extensions {
40
41namespace {
42
43// Return the menu label for a global error.
44base::string16 GetMenuItemLabel(const Extension* extension) {
45 if (!extension)
46 return base::string16();
47
48 int id = -1;
49 if (extension->is_app())
50 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
51 else if (extension->is_theme())
52 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
53 else
54 id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
55
56 return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension->name()));
57}
58
59// A global error that spawns a dialog when the menu item is clicked.
60class ExternalInstallMenuAlert : public GlobalError {
61 public:
62 explicit ExternalInstallMenuAlert(ExternalInstallError* error);
dchengae36a4a2014-10-21 12:36:3663 ~ExternalInstallMenuAlert() override;
[email protected]2894a512014-06-26 19:03:5664
65 private:
66 // GlobalError implementation.
dchengae36a4a2014-10-21 12:36:3667 Severity GetSeverity() override;
68 bool HasMenuItem() override;
69 int MenuItemCommandID() override;
70 base::string16 MenuItemLabel() override;
71 void ExecuteMenuItem(Browser* browser) override;
72 bool HasBubbleView() override;
73 bool HasShownBubbleView() override;
74 void ShowBubbleView(Browser* browser) override;
75 GlobalErrorBubbleViewBase* GetBubbleView() override;
[email protected]2894a512014-06-26 19:03:5676
77 // The owning ExternalInstallError.
78 ExternalInstallError* error_;
79
lazyboy0b9b30f2016-01-05 03:15:3780 // Provides menu item id for GlobalError.
81 ExtensionInstallErrorMenuItemIdProvider id_provider_;
82
[email protected]2894a512014-06-26 19:03:5683 DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert);
84};
85
86// A global error that spawns a bubble when the menu item is clicked.
87class ExternalInstallBubbleAlert : public GlobalErrorWithStandardBubble {
88 public:
rdevlin.cronin2e252692015-12-15 21:47:0289 ExternalInstallBubbleAlert(ExternalInstallError* error,
90 ExtensionInstallPrompt::Prompt* prompt);
dchengae36a4a2014-10-21 12:36:3691 ~ExternalInstallBubbleAlert() override;
[email protected]2894a512014-06-26 19:03:5692
93 private:
94 // GlobalError implementation.
dchengae36a4a2014-10-21 12:36:3695 Severity GetSeverity() override;
96 bool HasMenuItem() override;
97 int MenuItemCommandID() override;
98 base::string16 MenuItemLabel() override;
99 void ExecuteMenuItem(Browser* browser) override;
[email protected]2894a512014-06-26 19:03:56100
101 // GlobalErrorWithStandardBubble implementation.
dchengae36a4a2014-10-21 12:36:36102 gfx::Image GetBubbleViewIcon() override;
103 base::string16 GetBubbleViewTitle() override;
104 std::vector<base::string16> GetBubbleViewMessages() override;
105 base::string16 GetBubbleViewAcceptButtonLabel() override;
106 base::string16 GetBubbleViewCancelButtonLabel() override;
107 void OnBubbleViewDidClose(Browser* browser) override;
108 void BubbleViewAcceptButtonPressed(Browser* browser) override;
109 void BubbleViewCancelButtonPressed(Browser* browser) override;
[email protected]2894a512014-06-26 19:03:56110
111 // The owning ExternalInstallError.
112 ExternalInstallError* error_;
lazyboy0b9b30f2016-01-05 03:15:37113 ExtensionInstallErrorMenuItemIdProvider id_provider_;
[email protected]2894a512014-06-26 19:03:56114
115 // The Prompt with all information, which we then use to populate the bubble.
rdevlin.cronin2e252692015-12-15 21:47:02116 // Owned by |error|.
[email protected]2894a512014-06-26 19:03:56117 ExtensionInstallPrompt::Prompt* prompt_;
118
119 DISALLOW_COPY_AND_ASSIGN(ExternalInstallBubbleAlert);
120};
121
122////////////////////////////////////////////////////////////////////////////////
123// ExternalInstallMenuAlert
124
125ExternalInstallMenuAlert::ExternalInstallMenuAlert(ExternalInstallError* error)
126 : error_(error) {
127}
128
129ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
130}
131
132GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
133 return SEVERITY_LOW;
134}
135
136bool ExternalInstallMenuAlert::HasMenuItem() {
137 return true;
138}
139
140int ExternalInstallMenuAlert::MenuItemCommandID() {
lazyboy0b9b30f2016-01-05 03:15:37141 return id_provider_.menu_command_id();
[email protected]2894a512014-06-26 19:03:56142}
143
144base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
145 return GetMenuItemLabel(error_->GetExtension());
146}
147
148void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
149 error_->ShowDialog(browser);
150}
151
152bool ExternalInstallMenuAlert::HasBubbleView() {
153 return false;
154}
155
156bool ExternalInstallMenuAlert::HasShownBubbleView() {
157 NOTREACHED();
158 return true;
159}
160
161void ExternalInstallMenuAlert::ShowBubbleView(Browser* browser) {
162 NOTREACHED();
163}
164
165GlobalErrorBubbleViewBase* ExternalInstallMenuAlert::GetBubbleView() {
166 return NULL;
167}
168
169////////////////////////////////////////////////////////////////////////////////
170// ExternalInstallBubbleAlert
171
172ExternalInstallBubbleAlert::ExternalInstallBubbleAlert(
173 ExternalInstallError* error,
174 ExtensionInstallPrompt::Prompt* prompt)
175 : error_(error), prompt_(prompt) {
176 DCHECK(error_);
177 DCHECK(prompt_);
178}
179
180ExternalInstallBubbleAlert::~ExternalInstallBubbleAlert() {
181}
182
183GlobalError::Severity ExternalInstallBubbleAlert::GetSeverity() {
184 return SEVERITY_LOW;
185}
186
187bool ExternalInstallBubbleAlert::HasMenuItem() {
188 return true;
189}
190
191int ExternalInstallBubbleAlert::MenuItemCommandID() {
lazyboy0b9b30f2016-01-05 03:15:37192 return id_provider_.menu_command_id();
[email protected]2894a512014-06-26 19:03:56193}
194
195base::string16 ExternalInstallBubbleAlert::MenuItemLabel() {
196 return GetMenuItemLabel(error_->GetExtension());
197}
198
199void ExternalInstallBubbleAlert::ExecuteMenuItem(Browser* browser) {
lazyboy1899eec42016-03-08 19:00:50200 // |browser| is nullptr in unit test.
201 if (browser)
202 ShowBubbleView(browser);
203 error_->DidOpenBubbleView();
[email protected]2894a512014-06-26 19:03:56204}
205
206gfx::Image ExternalInstallBubbleAlert::GetBubbleViewIcon() {
207 if (prompt_->icon().IsEmpty())
208 return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
209 // Scale icon to a reasonable size.
210 return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
211 *prompt_->icon().ToImageSkia(),
212 skia::ImageOperations::RESIZE_BEST,
213 gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
214 extension_misc::EXTENSION_ICON_SMALL)));
215}
216
217base::string16 ExternalInstallBubbleAlert::GetBubbleViewTitle() {
treib5e16e452015-06-19 09:55:39218 return l10n_util::GetStringFUTF16(
219 IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_BUBBLE_TITLE,
220 base::UTF8ToUTF16(prompt_->extension()->name()));
[email protected]2894a512014-06-26 19:03:56221}
222
223std::vector<base::string16>
224ExternalInstallBubbleAlert::GetBubbleViewMessages() {
gpdavis.chromium0fbac4d2014-09-19 20:57:54225 ExtensionInstallPrompt::PermissionsType regular_permissions =
226 ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS;
227 ExtensionInstallPrompt::PermissionsType withheld_permissions =
228 ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS;
229
[email protected]2894a512014-06-26 19:03:56230 std::vector<base::string16> messages;
treib5e16e452015-06-19 09:55:39231 int heading_id =
232 IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_BUBBLE_HEADING_EXTENSION;
233 if (prompt_->extension()->is_app())
234 heading_id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_BUBBLE_HEADING_APP;
235 else if (prompt_->extension()->is_theme())
236 heading_id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_BUBBLE_HEADING_THEME;
237 messages.push_back(l10n_util::GetStringUTF16(heading_id));
238
gpdavis.chromium0fbac4d2014-09-19 20:57:54239 if (prompt_->GetPermissionCount(regular_permissions)) {
240 messages.push_back(prompt_->GetPermissionsHeading(regular_permissions));
241 for (size_t i = 0; i < prompt_->GetPermissionCount(regular_permissions);
242 ++i) {
[email protected]2894a512014-06-26 19:03:56243 messages.push_back(l10n_util::GetStringFUTF16(
gpdavis.chromium0fbac4d2014-09-19 20:57:54244 IDS_EXTENSION_PERMISSION_LINE,
245 prompt_->GetPermission(i, regular_permissions)));
246 }
247 }
248 if (prompt_->GetPermissionCount(withheld_permissions)) {
249 messages.push_back(prompt_->GetPermissionsHeading(withheld_permissions));
250 for (size_t i = 0; i < prompt_->GetPermissionCount(withheld_permissions);
251 ++i) {
252 messages.push_back(l10n_util::GetStringFUTF16(
253 IDS_EXTENSION_PERMISSION_LINE,
254 prompt_->GetPermission(i, withheld_permissions)));
[email protected]2894a512014-06-26 19:03:56255 }
256 }
257 // TODO(yoz): OAuth issue advice?
258 return messages;
259}
260
261base::string16 ExternalInstallBubbleAlert::GetBubbleViewAcceptButtonLabel() {
262 return prompt_->GetAcceptButtonLabel();
263}
264
265base::string16 ExternalInstallBubbleAlert::GetBubbleViewCancelButtonLabel() {
266 return prompt_->GetAbortButtonLabel();
267}
268
269void ExternalInstallBubbleAlert::OnBubbleViewDidClose(Browser* browser) {
lazyboy1899eec42016-03-08 19:00:50270 error_->DidCloseBubbleView();
[email protected]2894a512014-06-26 19:03:56271}
272
273void ExternalInstallBubbleAlert::BubbleViewAcceptButtonPressed(
274 Browser* browser) {
rdevlin.cronin41593052016-01-08 01:40:12275 error_->OnInstallPromptDone(ExtensionInstallPrompt::Result::ACCEPTED);
[email protected]2894a512014-06-26 19:03:56276}
277
278void ExternalInstallBubbleAlert::BubbleViewCancelButtonPressed(
279 Browser* browser) {
rdevlin.cronin41593052016-01-08 01:40:12280 error_->OnInstallPromptDone(ExtensionInstallPrompt::Result::USER_CANCELED);
[email protected]2894a512014-06-26 19:03:56281}
282
283} // namespace
284
285////////////////////////////////////////////////////////////////////////////////
286// ExternalInstallError
287
288ExternalInstallError::ExternalInstallError(
289 content::BrowserContext* browser_context,
290 const std::string& extension_id,
291 AlertType alert_type,
292 ExternalInstallManager* manager)
293 : browser_context_(browser_context),
294 extension_id_(extension_id),
295 alert_type_(alert_type),
296 manager_(manager),
297 error_service_(GlobalErrorServiceFactory::GetForProfile(
298 Profile::FromBrowserContext(browser_context_))),
299 weak_factory_(this) {
rdevlin.cronin2e252692015-12-15 21:47:02300 prompt_.reset(new ExtensionInstallPrompt::Prompt(
301 ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT));
[email protected]2894a512014-06-26 19:03:56302
303 webstore_data_fetcher_.reset(new WebstoreDataFetcher(
304 this, browser_context_->GetRequestContext(), GURL(), extension_id_));
305 webstore_data_fetcher_->Start();
306}
307
308ExternalInstallError::~ExternalInstallError() {
309 if (global_error_.get())
310 error_service_->RemoveGlobalError(global_error_.get());
311}
312
rdevlin.cronin41593052016-01-08 01:40:12313void ExternalInstallError::OnInstallPromptDone(
314 ExtensionInstallPrompt::Result result) {
[email protected]2894a512014-06-26 19:03:56315 const Extension* extension = GetExtension();
rdevlin.croninb2daf2e42016-01-14 20:00:54316
317 // If the error isn't removed and deleted as part of handling the user's
318 // response (which can happen, e.g., if an uninstall fails), be sure to remove
319 // the error directly in order to ensure it's not called twice.
320 base::MessageLoop::current()->PostTask(
321 FROM_HERE,
322 base::Bind(&ExternalInstallError::RemoveError,
323 weak_factory_.GetWeakPtr()));
324
rdevlin.cronin41593052016-01-08 01:40:12325 switch (result) {
326 case ExtensionInstallPrompt::Result::ACCEPTED:
327 if (extension) {
328 ExtensionSystem::Get(browser_context_)
329 ->extension_service()
330 ->GrantPermissionsAndEnableExtension(extension);
rdevlin.cronin41593052016-01-08 01:40:12331 }
332 break;
333 case ExtensionInstallPrompt::Result::USER_CANCELED:
334 if (extension) {
rdevlin.croninb2daf2e42016-01-14 20:00:54335 bool uninstallation_result = ExtensionSystem::Get(browser_context_)
rdevlin.cronin41593052016-01-08 01:40:12336 ->extension_service()
337 ->UninstallExtension(extension_id_,
338 extensions::UNINSTALL_REASON_INSTALL_CANCELED,
339 base::Bind(&base::DoNothing),
340 nullptr); // Ignore error.
rdevlin.croninb2daf2e42016-01-14 20:00:54341 UMA_HISTOGRAM_BOOLEAN("Extensions.ExternalWarningUninstallationResult",
342 uninstallation_result);
rdevlin.cronin41593052016-01-08 01:40:12343 }
344 break;
345 case ExtensionInstallPrompt::Result::ABORTED:
lazyboy1899eec42016-03-08 19:00:50346 manager_->DidChangeInstallAlertVisibility(this, false);
rdevlin.cronin41593052016-01-08 01:40:12347 break;
[email protected]2894a512014-06-26 19:03:56348 }
rdevlin.croninb2daf2e42016-01-14 20:00:54349 // NOTE: We may be deleted here!
[email protected]2894a512014-06-26 19:03:56350}
351
lazyboy1899eec42016-03-08 19:00:50352void ExternalInstallError::DidOpenBubbleView() {
353 manager_->DidChangeInstallAlertVisibility(this, true);
354}
355
356void ExternalInstallError::DidCloseBubbleView() {
357 manager_->DidChangeInstallAlertVisibility(this, false);
358}
359
[email protected]2894a512014-06-26 19:03:56360void ExternalInstallError::ShowDialog(Browser* browser) {
361 DCHECK(install_ui_.get());
362 DCHECK(prompt_.get());
363 DCHECK(browser);
364 content::WebContents* web_contents = NULL;
[email protected]2894a512014-06-26 19:03:56365 web_contents = browser->tab_strip_model()->GetActiveWebContents();
pkotwicz2f181782014-10-29 17:33:45366 install_ui_show_params_.reset(
367 new ExtensionInstallPromptShowParams(web_contents));
lazyboy1899eec42016-03-08 19:00:50368 manager_->DidChangeInstallAlertVisibility(this, true);
[email protected]2894a512014-06-26 19:03:56369 ExtensionInstallPrompt::GetDefaultShowDialogCallback().Run(
rdevlin.cronin41593052016-01-08 01:40:12370 install_ui_show_params_.get(),
371 base::Bind(&ExternalInstallError::OnInstallPromptDone,
372 weak_factory_.GetWeakPtr()),
373 std::move(prompt_));
[email protected]2894a512014-06-26 19:03:56374}
375
376const Extension* ExternalInstallError::GetExtension() const {
377 return ExtensionRegistry::Get(browser_context_)
378 ->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
379}
380
381void ExternalInstallError::OnWebstoreRequestFailure() {
382 OnFetchComplete();
383}
384
385void ExternalInstallError::OnWebstoreResponseParseSuccess(
dchengc963c7142016-04-08 03:55:22386 std::unique_ptr<base::DictionaryValue> webstore_data) {
[email protected]2894a512014-06-26 19:03:56387 std::string localized_user_count;
[email protected]96aebe22014-07-16 04:07:51388 double average_rating = 0;
389 int rating_count = 0;
[email protected]2894a512014-06-26 19:03:56390 if (!webstore_data->GetString(kUsersKey, &localized_user_count) ||
391 !webstore_data->GetDouble(kAverageRatingKey, &average_rating) ||
392 !webstore_data->GetInteger(kRatingCountKey, &rating_count)) {
393 // If we don't get a valid webstore response, short circuit, and continue
394 // to show a prompt without webstore data.
395 OnFetchComplete();
396 return;
397 }
398
399 bool show_user_count = true;
400 webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
401
402 prompt_->SetWebstoreData(
403 localized_user_count, show_user_count, average_rating, rating_count);
404 OnFetchComplete();
405}
406
407void ExternalInstallError::OnWebstoreResponseParseFailure(
408 const std::string& error) {
409 OnFetchComplete();
410}
411
412void ExternalInstallError::OnFetchComplete() {
413 // Create a new ExtensionInstallPrompt. We pass in NULL for the UI
414 // components because we display at a later point, and don't want
415 // to pass ones which may be invalidated.
416 install_ui_.reset(
417 new ExtensionInstallPrompt(Profile::FromBrowserContext(browser_context_),
pkotwicz2175c622014-10-22 19:56:28418 NULL)); // NULL native window.
[email protected]2894a512014-06-26 19:03:56419
rdevlin.cronin41593052016-01-08 01:40:12420 install_ui_->ShowDialog(base::Bind(&ExternalInstallError::OnInstallPromptDone,
421 weak_factory_.GetWeakPtr()),
422 GetExtension(),
rdevlin.croninf84cab72015-12-12 03:45:23423 nullptr, // Force a fetch of the icon.
dcheng1fc00f12015-12-26 22:18:03424 std::move(prompt_),
rdevlin.croninf84cab72015-12-12 03:45:23425 base::Bind(&ExternalInstallError::OnDialogReady,
426 weak_factory_.GetWeakPtr()));
[email protected]2894a512014-06-26 19:03:56427}
428
429void ExternalInstallError::OnDialogReady(
pkotwicz2f181782014-10-29 17:33:45430 ExtensionInstallPromptShowParams* show_params,
rdevlin.cronin41593052016-01-08 01:40:12431 const ExtensionInstallPrompt::DoneCallback& callback,
dchengc963c7142016-04-08 03:55:22432 std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
dcheng1fc00f12015-12-26 22:18:03433 prompt_ = std::move(prompt);
[email protected]2894a512014-06-26 19:03:56434
435 if (alert_type_ == BUBBLE_ALERT) {
dchengc7047942014-08-26 05:05:31436 global_error_.reset(new ExternalInstallBubbleAlert(this, prompt_.get()));
[email protected]2894a512014-06-26 19:03:56437 error_service_->AddGlobalError(global_error_.get());
438
lazyboy1899eec42016-03-08 19:00:50439 if (!manager_->has_currently_visible_install_alert()) {
440 // |browser| is nullptr during unit tests, so call
441 // DidChangeInstallAlertVisibility() regardless because we depend on this
442 // in unit tests.
443 manager_->DidChangeInstallAlertVisibility(this, true);
444 Browser* browser = chrome::FindTabbedBrowser(
445 Profile::FromBrowserContext(browser_context_), true);
446 if (browser)
447 global_error_->ShowBubbleView(browser);
448 }
[email protected]2894a512014-06-26 19:03:56449 } else {
450 DCHECK(alert_type_ == MENU_ALERT);
451 global_error_.reset(new ExternalInstallMenuAlert(this));
452 error_service_->AddGlobalError(global_error_.get());
453 }
454}
455
rdevlin.croninb2daf2e42016-01-14 20:00:54456void ExternalInstallError::RemoveError() {
457 manager_->RemoveExternalInstallError(extension_id_);
458}
459
[email protected]2894a512014-06-26 19:03:56460} // namespace extensions