blob: e8953ab88e5bf02e12b06e6f0aa526bda855d410 [file] [log] [blame]
[email protected]e3c0bc22012-02-24 01:34:151// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]502e39612011-03-26 01:36:282// 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_uninstall_dialog.h"
6
[email protected]0d9a1da82013-03-14 21:52:077#include "base/bind.h"
Dominick Ng2a47d2f2019-01-08 09:05:018#include "base/bind_helpers.h"
rdevlin.cronin4f01c5f12015-05-07 17:52:009#include "base/metrics/histogram_macros.h"
[email protected]226d79ad2014-03-14 21:34:5710#include "base/strings/utf_string_conversions.h"
khmel0df822022017-06-23 02:02:5611#include "chrome/browser/extensions/chrome_app_icon_service.h"
rdevlin.croninac8ce8c2015-05-27 23:00:0212#include "chrome/browser/extensions/extension_service.h"
[email protected]7eb20e32014-04-30 08:50:5613#include "chrome/browser/extensions/extension_util.h"
[email protected]472522b2013-10-25 00:41:2814#include "chrome/browser/profiles/profile.h"
rdevlin.croninf3af70d52015-03-20 03:55:0415#include "chrome/browser/ui/browser_navigator.h"
thestige80821242015-09-30 23:46:0816#include "chrome/browser/ui/browser_navigator_params.h"
khmel0df822022017-06-23 02:02:5617#include "chrome/browser/ui/native_window_tracker.h"
Dominick Ng2a47d2f2019-01-08 09:05:0118#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
Dominick Ng98e88222019-01-14 00:38:5719#include "chrome/grit/chromium_strings.h"
[email protected]af39f002014-08-22 10:18:1820#include "chrome/grit/generated_resources.h"
Dominick Ng2a47d2f2019-01-08 09:05:0121#include "components/url_formatter/elide_url.h"
22#include "content/public/browser/clear_site_data_utils.h"
rdevlin.cronin66209492015-06-10 20:44:0523#include "extensions/browser/extension_dialog_auto_confirm.h"
rdevlin.croninac8ce8c2015-05-27 23:00:0224#include "extensions/browser/extension_system.h"
[email protected]326e6f02014-06-20 04:53:3725#include "extensions/browser/image_loader.h"
[email protected]cda103d2014-04-04 16:22:3926#include "extensions/common/constants.h"
[email protected]e4452d32013-11-15 23:07:4127#include "extensions/common/extension.h"
rdevlin.croninf3af70d52015-03-20 03:55:0428#include "extensions/common/extension_urls.h"
[email protected]0db486f2014-04-09 19:32:2229#include "extensions/common/manifest_handlers/icons_handler.h"
rdevlin.cronindbb8fa52015-05-06 00:16:5630#include "extensions/common/manifest_url_handlers.h"
[email protected]226d79ad2014-03-14 21:34:5731#include "ui/base/l10n/l10n_util.h"
khmel0df822022017-06-23 02:02:5632#include "ui/base/layout.h"
rdevlin.croninf3af70d52015-03-20 03:55:0433#include "ui/base/page_transition_types.h"
34#include "ui/base/window_open_disposition.h"
khmel0df822022017-06-23 02:02:5635#include "ui/display/display.h"
36#include "ui/display/screen.h"
[email protected]bdd6eec2012-03-03 19:58:0637#include "ui/gfx/image/image.h"
[email protected]50b66262013-09-24 03:25:4838#include "ui/gfx/image/image_skia.h"
Dominick Ng2a47d2f2019-01-08 09:05:0139#include "url/origin.h"
[email protected]502e39612011-03-26 01:36:2840
[email protected]d4050672014-06-04 09:18:4041namespace extensions {
42
[email protected]dd46a4ce2012-09-15 10:50:5043namespace {
44
khmel0df822022017-06-23 02:02:5645constexpr int kIconSize = 64;
46
47constexpr char kExtensionRemovedError[] =
rdevlin.croninac8ce8c2015-05-27 23:00:0248 "Extension was removed before dialog closed.";
49
khmel0df822022017-06-23 02:02:5650constexpr char kReferrerId[] = "chrome-remove-extension-dialog";
rdevlin.croninde23cac2015-07-15 23:51:3951
khmel0df822022017-06-23 02:02:5652float GetScaleFactor(gfx::NativeWindow window) {
53 const display::Screen* screen = display::Screen::GetScreen();
54 if (!screen)
55 return 1.0; // Happens in unit_tests.
56 if (window)
57 return screen->GetDisplayNearestWindow(window).device_scale_factor();
58 return screen->GetPrimaryDisplay().device_scale_factor();
[email protected]dd46a4ce2012-09-15 10:50:5059}
60
Bettina Dea7264a04b62018-04-06 04:27:4561ExtensionUninstallDialog::OnWillShowCallback* g_on_will_show_callback = nullptr;
[email protected]dd46a4ce2012-09-15 10:50:5062} // namespace
63
Bettina Dea7264a04b62018-04-06 04:27:4564void ExtensionUninstallDialog::SetOnShownCallbackForTesting(
65 ExtensionUninstallDialog::OnWillShowCallback* callback) {
66 g_on_will_show_callback = callback;
67}
68
[email protected]6f03db062011-09-22 20:37:1469ExtensionUninstallDialog::ExtensionUninstallDialog(
[email protected]520414b12013-01-22 19:27:3670 Profile* profile,
khmel0df822022017-06-23 02:02:5671 gfx::NativeWindow parent,
[email protected]6f03db062011-09-22 20:37:1472 ExtensionUninstallDialog::Delegate* delegate)
Evan Stade75872a62019-09-06 21:17:3873 : profile_(profile), parent_(parent), delegate_(delegate) {
khmel0df822022017-06-23 02:02:5674 if (parent)
75 parent_window_tracker_ = NativeWindowTracker::Create(parent);
[email protected]5f1ba412012-06-25 20:10:5276}
[email protected]502e39612011-03-26 01:36:2877
khmel0df822022017-06-23 02:02:5678ExtensionUninstallDialog::~ExtensionUninstallDialog() = default;
[email protected]502e39612011-03-26 01:36:2879
rdevlin.croninac8ce8c2015-05-27 23:00:0280void ExtensionUninstallDialog::ConfirmUninstallByExtension(
81 const scoped_refptr<const Extension>& extension,
82 const scoped_refptr<const Extension>& triggering_extension,
rdevlin.cronine18eb8cd2015-07-16 16:31:2883 UninstallReason reason,
84 UninstallSource source) {
[email protected]226d79ad2014-03-14 21:34:5785 triggering_extension_ = triggering_extension;
rdevlin.cronine18eb8cd2015-07-16 16:31:2886 ConfirmUninstall(extension, reason, source);
[email protected]226d79ad2014-03-14 21:34:5787}
88
rdevlin.croninac8ce8c2015-05-27 23:00:0289void ExtensionUninstallDialog::ConfirmUninstall(
90 const scoped_refptr<const Extension>& extension,
rdevlin.cronine18eb8cd2015-07-16 16:31:2891 UninstallReason reason,
92 UninstallSource source) {
rdevlin.croninac8ce8c2015-05-27 23:00:0293 DCHECK(thread_checker_.CalledOnValidThread());
rdevlin.cronine18eb8cd2015-07-16 16:31:2894
95 UMA_HISTOGRAM_ENUMERATION("Extensions.UninstallSource", source,
96 NUM_UNINSTALL_SOURCES);
97
[email protected]502e39612011-03-26 01:36:2898 extension_ = extension;
rdevlin.croninac8ce8c2015-05-27 23:00:0299 uninstall_reason_ = reason;
[email protected]eed85492013-01-14 20:37:46100
khmel0df822022017-06-23 02:02:56101 if (parent() && parent_window_tracker_->WasNativeWindowClosed()) {
102 OnDialogClosed(CLOSE_ACTION_CANCELED);
[email protected]e0ebb8d2014-05-21 20:26:27103 return;
104 }
105
khmel0df822022017-06-23 02:02:56106 // Track that extension uninstalled externally.
107 DCHECK(!observer_.IsObserving(ExtensionRegistry::Get(profile_)));
108 observer_.Add(ExtensionRegistry::Get(profile_));
109
110 // Dialog will be shown once icon is loaded.
111 DCHECK(!dialog_shown_);
112 icon_ = ChromeAppIconService::Get(profile_)->CreateIcon(this, extension->id(),
113 kIconSize);
114 icon_->image_skia().GetRepresentation(GetScaleFactor(parent_));
115}
116
117void ExtensionUninstallDialog::OnIconUpdated(ChromeAppIcon* icon) {
118 // Ignore initial update.
119 if (!icon_ || dialog_shown_)
120 return;
khmel0df822022017-06-23 02:02:56121 DCHECK_EQ(icon, icon_.get());
122
123 dialog_shown_ = true;
124
125 if (parent() && parent_window_tracker_->WasNativeWindowClosed()) {
126 OnDialogClosed(CLOSE_ACTION_CANCELED);
127 return;
128 }
rdevlin.croninac8ce8c2015-05-27 23:00:02129
Bettina Dea7264a04b62018-04-06 04:27:45130 if (g_on_will_show_callback != nullptr)
131 g_on_will_show_callback->Run(this);
132
rdevlin.cronin66209492015-06-10 20:44:05133 switch (ScopedTestDialogAutoConfirm::GetAutoConfirmValue()) {
134 case ScopedTestDialogAutoConfirm::NONE:
rdevlin.croninac8ce8c2015-05-27 23:00:02135 Show();
136 break;
catmullingsff559d92017-05-20 01:43:36137 case ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION:
Dominick Ng2a47d2f2019-01-08 09:05:01138 OnDialogClosed(CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED);
catmullingsff559d92017-05-20 01:43:36139 break;
rdevlin.cronin66209492015-06-10 20:44:05140 case ScopedTestDialogAutoConfirm::ACCEPT:
rdevlin.croninac8ce8c2015-05-27 23:00:02141 OnDialogClosed(CLOSE_ACTION_UNINSTALL);
142 break;
rdevlin.cronin66209492015-06-10 20:44:05143 case ScopedTestDialogAutoConfirm::CANCEL:
rdevlin.croninac8ce8c2015-05-27 23:00:02144 OnDialogClosed(CLOSE_ACTION_CANCELED);
145 break;
146 }
[email protected]5f1ba412012-06-25 20:10:52147}
[email protected]226d79ad2014-03-14 21:34:57148
khmel0df822022017-06-23 02:02:56149void ExtensionUninstallDialog::OnExtensionUninstalled(
150 content::BrowserContext* browser_context,
151 const Extension* extension,
152 UninstallReason reason) {
153 // Handle the case when extension was uninstalled externally and we have to
154 // close current dialog.
155 if (extension != extension_)
156 return;
157
158 delegate_->OnExtensionUninstallDialogClosed(
159 false, base::ASCIIToUTF16(kExtensionRemovedError));
160}
161
[email protected]226d79ad2014-03-14 21:34:57162std::string ExtensionUninstallDialog::GetHeadingText() {
163 if (triggering_extension_) {
164 return l10n_util::GetStringFUTF8(
165 IDS_EXTENSION_PROGRAMMATIC_UNINSTALL_PROMPT_HEADING,
166 base::UTF8ToUTF16(triggering_extension_->name()),
167 base::UTF8ToUTF16(extension_->name()));
168 }
169 return l10n_util::GetStringFUTF8(IDS_EXTENSION_UNINSTALL_PROMPT_HEADING,
170 base::UTF8ToUTF16(extension_->name()));
171}
[email protected]d4050672014-06-04 09:18:40172
Dominick Ng2a47d2f2019-01-08 09:05:01173GURL ExtensionUninstallDialog::GetLaunchURL() const {
174 return AppLaunchInfo::GetFullLaunchURL(extension_.get());
175}
176
177bool ExtensionUninstallDialog::ShouldShowCheckbox() const {
178 return ShouldShowReportAbuseCheckbox() || ShouldShowRemoveDataCheckbox();
179}
180
181base::string16 ExtensionUninstallDialog::GetCheckboxLabel() const {
182 DCHECK(ShouldShowCheckbox());
183
184 if (ShouldShowReportAbuseCheckbox()) {
185 return triggering_extension_.get()
186 ? l10n_util::GetStringFUTF16(
187 IDS_EXTENSION_PROMPT_UNINSTALL_REPORT_ABUSE_FROM_EXTENSION,
188 base::UTF8ToUTF16(extension_->name()))
189 : l10n_util::GetStringUTF16(
190 IDS_EXTENSION_PROMPT_UNINSTALL_REPORT_ABUSE);
191 }
192
193 DCHECK(ShouldShowRemoveDataCheckbox());
194 return l10n_util::GetStringFUTF16(
195 IDS_EXTENSION_UNINSTALL_PROMPT_REMOVE_DATA_CHECKBOX,
196 url_formatter::FormatUrlForSecurityDisplay(
197 GetLaunchURL(), url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
rdevlin.croninf3af70d52015-03-20 03:55:04198}
199
rdevlin.cronin4f01c5f12015-05-07 17:52:00200void ExtensionUninstallDialog::OnDialogClosed(CloseAction action) {
201 // We don't want to artificially weight any of the options, so only record if
Dominick Ng2a47d2f2019-01-08 09:05:01202 // a checkbox was shown.
rdevlin.cronin4f01c5f12015-05-07 17:52:00203 if (ShouldShowReportAbuseCheckbox()) {
Dominick Ng2a47d2f2019-01-08 09:05:01204 UMA_HISTOGRAM_ENUMERATION("Extensions.UninstallDialogAction", action,
205 CLOSE_ACTION_LAST);
206 } else if (ShouldShowRemoveDataCheckbox()) {
207 UMA_HISTOGRAM_ENUMERATION("Webapp.UninstallDialogAction", action,
rdevlin.cronin4f01c5f12015-05-07 17:52:00208 CLOSE_ACTION_LAST);
209 }
rdevlin.croninac8ce8c2015-05-27 23:00:02210
211 bool success = false;
212 base::string16 error;
213 switch (action) {
Dominick Ng2a47d2f2019-01-08 09:05:01214 case CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED:
catmullingsff559d92017-05-20 01:43:36215 success = Uninstall(&error);
Dominick Ng2a47d2f2019-01-08 09:05:01216 if (ShouldShowRemoveDataCheckbox()) {
217 content::ClearSiteData(
218 base::BindRepeating(
219 [](content::BrowserContext* browser_context) {
220 return browser_context;
221 },
222 base::Unretained(profile_)),
223 url::Origin::Create(GetLaunchURL()), true /*clear_cookies*/,
224 true /*clear_storage*/, true /*clear_cache*/,
225 false /*avoid_closing_connections*/, base::DoNothing());
226 } else {
227 // If the extension specifies a custom uninstall page via
228 // chrome.runtime.setUninstallURL, then at uninstallation its uninstall
229 // page opens. To ensure that the CWS Report Abuse page is the active
230 // tab at uninstallation, HandleReportAbuse() is called after
231 // Uninstall().
232 HandleReportAbuse();
233 }
catmullingsff559d92017-05-20 01:43:36234 break;
Dominick Ng2a47d2f2019-01-08 09:05:01235 case CLOSE_ACTION_UNINSTALL:
catmullingsff559d92017-05-20 01:43:36236 success = Uninstall(&error);
rdevlin.croninac8ce8c2015-05-27 23:00:02237 break;
rdevlin.croninac8ce8c2015-05-27 23:00:02238 case CLOSE_ACTION_CANCELED:
239 error = base::ASCIIToUTF16("User canceled uninstall dialog");
240 break;
241 case CLOSE_ACTION_LAST:
242 NOTREACHED();
243 }
rdevlin.croninac8ce8c2015-05-27 23:00:02244 delegate_->OnExtensionUninstallDialogClosed(success, error);
rdevlin.cronin4f01c5f12015-05-07 17:52:00245}
246
catmullingsff559d92017-05-20 01:43:36247bool ExtensionUninstallDialog::Uninstall(base::string16* error) {
248 const Extension* current_extension =
249 ExtensionRegistry::Get(profile_)->GetExtensionById(
250 extension_->id(), ExtensionRegistry::EVERYTHING);
251 if (current_extension) {
khmel0df822022017-06-23 02:02:56252 // Prevent notifications triggered by our request.
253 observer_.RemoveAll();
catmullingsff559d92017-05-20 01:43:36254 return ExtensionSystem::Get(profile_)
255 ->extension_service()
Devlin Cronin218df7f2017-11-21 21:41:31256 ->UninstallExtension(extension_->id(), uninstall_reason_, error);
catmullingsff559d92017-05-20 01:43:36257 }
258 *error = base::ASCIIToUTF16(kExtensionRemovedError);
259 return false;
260}
261
rdevlin.croninf3af70d52015-03-20 03:55:04262void ExtensionUninstallDialog::HandleReportAbuse() {
cm.sanchi2522bc92017-12-04 08:04:13263 NavigateParams params(
rdevlin.croninf3af70d52015-03-20 03:55:04264 profile_,
rdevlin.croninde23cac2015-07-15 23:51:39265 extension_urls::GetWebstoreReportAbuseUrl(extension_->id(), kReferrerId),
rdevlin.croninf3af70d52015-03-20 03:55:04266 ui::PAGE_TRANSITION_LINK);
nick3b04f322016-08-31 19:29:19267 params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
cm.sanchi2522bc92017-12-04 08:04:13268 Navigate(&params);
rdevlin.croninf3af70d52015-03-20 03:55:04269}
270
Dominick Ng2a47d2f2019-01-08 09:05:01271bool ExtensionUninstallDialog::ShouldShowReportAbuseCheckbox() const {
272 return ManifestURL::UpdatesFromGallery(extension_.get());
273}
274
275bool ExtensionUninstallDialog::ShouldShowRemoveDataCheckbox() const {
276 return extension_->from_bookmark();
277}
278
[email protected]d4050672014-06-04 09:18:40279} // namespace extensions