1 // 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_disabled_ui.h"
6
7 #include <memory>
8 #include <string>
9
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/macros.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/scoped_observer.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "chrome/browser/extensions/extension_install_error_menu_item_id_provider.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
22 #include "chrome/browser/extensions/extension_util.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_window.h"
26 #include "chrome/browser/ui/global_error/global_error.h"
27 #include "chrome/browser/ui/global_error/global_error_service.h"
28 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
29 #include "chrome/browser/ui/tabs/tab_strip_model.h"
30 #include "chrome/grit/generated_resources.h"
31 #include "components/strings/grit/components_strings.h"
32 #include "content/public/browser/notification_details.h"
33 #include "content/public/browser/notification_observer.h"
34 #include "content/public/browser/notification_registrar.h"
35 #include "content/public/browser/notification_source.h"
36 #include "extensions/browser/extension_registry.h"
37 #include "extensions/browser/extension_registry_observer.h"
38 #include "extensions/browser/extension_util.h"
39 #include "extensions/browser/image_loader.h"
40 #include "extensions/browser/notification_types.h"
41 #include "extensions/browser/uninstall_reason.h"
42 #include "extensions/common/extension.h"
43 #include "extensions/common/extension_icon_set.h"
44 #include "extensions/common/permissions/permission_message.h"
45 #include "extensions/common/permissions/permissions_data.h"
46 #include "ui/base/l10n/l10n_util.h"
47 #include "ui/gfx/geometry/size.h"
48
49 // ExtensionDisabledGlobalError -----------------------------------------------
50
51 namespace extensions {
52
53 class ExtensionDisabledGlobalError : public GlobalErrorWithStandardBubble,
54 public content::NotificationObserver,
55 public ExtensionUninstallDialog::Delegate,
56 public ExtensionRegistryObserver {
57 public:
58 ExtensionDisabledGlobalError(ExtensionService* service,
59 const Extension* extension,
60 bool is_remote_install);
61 ~ExtensionDisabledGlobalError() override;
62
63 // GlobalError:
64 Severity GetSeverity() override;
65 bool HasMenuItem() override;
66 int MenuItemCommandID() override;
67 base::string16 MenuItemLabel() override;
68 void ExecuteMenuItem(Browser* browser) override;
69 base::string16 GetBubbleViewTitle() override;
70 std::vector<base::string16> GetBubbleViewMessages() override;
71 base::string16 GetBubbleViewAcceptButtonLabel() override;
72 base::string16 GetBubbleViewCancelButtonLabel() override;
73 void OnBubbleViewDidClose(Browser* browser) override;
74 void BubbleViewAcceptButtonPressed(Browser* browser) override;
75 void BubbleViewCancelButtonPressed(Browser* browser) override;
76 bool ShouldCloseOnDeactivate() const override;
77 bool ShouldShowCloseButton() const override;
78
79 // ExtensionUninstallDialog::Delegate:
80 void OnExtensionUninstallDialogClosed(bool did_start_uninstall,
81 const base::string16& error) override;
82
83 private:
84 // content::NotificationObserver:
85 void Observe(int type,
86 const content::NotificationSource& source,
87 const content::NotificationDetails& details) override;
88
89 // ExtensionRegistryObserver:
90 void OnExtensionLoaded(content::BrowserContext* browser_context,
91 const Extension* extension) override;
92 void OnShutdown(ExtensionRegistry* registry) override;
93
94 void RemoveGlobalError();
95
96 ExtensionService* service_;
97 const Extension* extension_;
98 bool is_remote_install_;
99
100 // How the user responded to the error; used for metrics.
101 enum UserResponse {
102 IGNORED,
103 REENABLE,
104 UNINSTALL,
105 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY
106 };
107 UserResponse user_response_;
108
109 std::unique_ptr<ExtensionUninstallDialog> uninstall_dialog_;
110
111 // Helper to get menu command ID assigned for this extension's error.
112 ExtensionInstallErrorMenuItemIdProvider id_provider_;
113
114 content::NotificationRegistrar registrar_;
115
116 ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
117 registry_observer_{this};
118
119 DISALLOW_COPY_AND_ASSIGN(ExtensionDisabledGlobalError);
120 };
121
122 // TODO(yoz): create error at startup for disabled extensions.
ExtensionDisabledGlobalError(ExtensionService * service,const Extension * extension,bool is_remote_install)123 ExtensionDisabledGlobalError::ExtensionDisabledGlobalError(
124 ExtensionService* service,
125 const Extension* extension,
126 bool is_remote_install)
127 : service_(service),
128 extension_(extension),
129 is_remote_install_(is_remote_install),
130 user_response_(IGNORED) {
131 registry_observer_.Add(ExtensionRegistry::Get(service->profile()));
132 registrar_.Add(this, NOTIFICATION_EXTENSION_REMOVED,
133 content::Source<Profile>(service->profile()));
134 }
135
~ExtensionDisabledGlobalError()136 ExtensionDisabledGlobalError::~ExtensionDisabledGlobalError() {}
137
GetSeverity()138 GlobalError::Severity ExtensionDisabledGlobalError::GetSeverity() {
139 return SEVERITY_LOW;
140 }
141
HasMenuItem()142 bool ExtensionDisabledGlobalError::HasMenuItem() {
143 return true;
144 }
145
MenuItemCommandID()146 int ExtensionDisabledGlobalError::MenuItemCommandID() {
147 return id_provider_.menu_command_id();
148 }
149
MenuItemLabel()150 base::string16 ExtensionDisabledGlobalError::MenuItemLabel() {
151 std::string extension_name = extension_->name();
152 // Ampersands need to be escaped to avoid being treated like
153 // mnemonics in the menu.
154 base::ReplaceChars(extension_name, "&", "&&", &extension_name);
155
156 if (is_remote_install_) {
157 return l10n_util::GetStringFUTF16(
158 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
159 base::UTF8ToUTF16(extension_name));
160 } else {
161 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
162 base::UTF8ToUTF16(extension_name));
163 }
164 }
165
ExecuteMenuItem(Browser * browser)166 void ExtensionDisabledGlobalError::ExecuteMenuItem(Browser* browser) {
167 ShowBubbleView(browser);
168 }
169
GetBubbleViewTitle()170 base::string16 ExtensionDisabledGlobalError::GetBubbleViewTitle() {
171 if (is_remote_install_) {
172 return l10n_util::GetStringFUTF16(
173 IDS_EXTENSION_DISABLED_REMOTE_INSTALL_ERROR_TITLE,
174 base::UTF8ToUTF16(extension_->name()));
175 } else {
176 return l10n_util::GetStringFUTF16(IDS_EXTENSION_DISABLED_ERROR_TITLE,
177 base::UTF8ToUTF16(extension_->name()));
178 }
179 }
180
181 std::vector<base::string16>
GetBubbleViewMessages()182 ExtensionDisabledGlobalError::GetBubbleViewMessages() {
183 std::vector<base::string16> messages;
184
185 std::unique_ptr<const PermissionSet> granted_permissions =
186 ExtensionPrefs::Get(service_->GetBrowserContext())
187 ->GetGrantedPermissions(extension_->id());
188
189 PermissionMessages permission_warnings =
190 extension_->permissions_data()->GetNewPermissionMessages(
191 *granted_permissions);
192
193 if (is_remote_install_) {
194 if (!permission_warnings.empty())
195 messages.push_back(
196 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO));
197 } else {
198 // TODO(crbug.com/461261): If NeedCustodianApprovalForPermissionIncrease,
199 // add an extra message for supervised users.
200 messages.push_back(
201 l10n_util::GetStringUTF16(IDS_EXTENSION_DISABLED_ERROR_LABEL));
202 }
203 for (const PermissionMessage& msg : permission_warnings) {
204 messages.push_back(l10n_util::GetStringFUTF16(IDS_EXTENSION_PERMISSION_LINE,
205 msg.message()));
206 }
207 return messages;
208 }
209
GetBubbleViewAcceptButtonLabel()210 base::string16 ExtensionDisabledGlobalError::GetBubbleViewAcceptButtonLabel() {
211 if (is_remote_install_) {
212 return l10n_util::GetStringUTF16(
213 extension_->is_app()
214 ? IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_APP
215 : IDS_EXTENSION_PROMPT_REMOTE_INSTALL_BUTTON_EXTENSION);
216 }
217 return l10n_util::GetStringUTF16(
218 IDS_EXTENSION_PROMPT_PERMISSIONS_ACCEPT_BUTTON);
219 }
220
GetBubbleViewCancelButtonLabel()221 base::string16 ExtensionDisabledGlobalError::GetBubbleViewCancelButtonLabel() {
222 return l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON);
223 }
224
OnBubbleViewDidClose(Browser * browser)225 void ExtensionDisabledGlobalError::OnBubbleViewDidClose(Browser* browser) {
226 // If the user takes an action, |user_response_| is set in
227 // BubbleView[Cancel|Accept]Pressed(). Otherwise, the IGNORE value set in the
228 // constructor is correct.
229 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponseRemoteInstall2",
230 user_response_,
231 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
232 UMA_HISTOGRAM_ENUMERATION("Extensions.DisabledUIUserResponse2",
233 user_response_,
234 EXTENSION_DISABLED_UI_BUCKET_BOUNDARY);
235 // Reset in case the user does not follow through on subsequent dialogs to
236 // confirm removal decision, in which case the bubble can be shown again
237 // when the user clicks on the global error in the menu.
238 user_response_ = IGNORED;
239 }
240
BubbleViewAcceptButtonPressed(Browser * browser)241 void ExtensionDisabledGlobalError::BubbleViewAcceptButtonPressed(
242 Browser* browser) {
243 user_response_ = REENABLE;
244 // Delay extension reenabling so this bubble closes properly.
245 base::ThreadTaskRunnerHandle::Get()->PostTask(
246 FROM_HERE,
247 base::BindOnce(&ExtensionService::GrantPermissionsAndEnableExtension,
248 service_->AsWeakPtr(), base::RetainedRef(extension_)));
249 }
250
BubbleViewCancelButtonPressed(Browser * browser)251 void ExtensionDisabledGlobalError::BubbleViewCancelButtonPressed(
252 Browser* browser) {
253 uninstall_dialog_ = ExtensionUninstallDialog::Create(
254 service_->profile(), browser->window()->GetNativeWindow(), this);
255 user_response_ = UNINSTALL;
256 // Delay showing the uninstall dialog, so that this function returns
257 // immediately, to close the bubble properly. See crbug.com/121544.
258 base::ThreadTaskRunnerHandle::Get()->PostTask(
259 FROM_HERE, base::BindOnce(&ExtensionUninstallDialog::ConfirmUninstall,
260 uninstall_dialog_->AsWeakPtr(),
261 base::RetainedRef(extension_),
262 UNINSTALL_REASON_EXTENSION_DISABLED,
263 UNINSTALL_SOURCE_PERMISSIONS_INCREASE));
264 }
265
ShouldCloseOnDeactivate() const266 bool ExtensionDisabledGlobalError::ShouldCloseOnDeactivate() const {
267 // Since this indicates that an extension was disabled, we should definitely
268 // have the user acknowledge it, rather than having the bubble disappear when
269 // a new window pops up.
270 return false;
271 }
272
ShouldShowCloseButton() const273 bool ExtensionDisabledGlobalError::ShouldShowCloseButton() const {
274 // As we don't close the bubble on deactivation (see ShouldCloseOnDeactivate),
275 // we add a close button so the user doesn't *need* to act right away.
276 // If the bubble is closed, the error remains in the wrench menu and the user
277 // can address it later.
278 return true;
279 }
280
OnExtensionUninstallDialogClosed(bool did_start_uninstall,const base::string16 & error)281 void ExtensionDisabledGlobalError::OnExtensionUninstallDialogClosed(
282 bool did_start_uninstall,
283 const base::string16& error) {
284 // No need to do anything.
285 }
286
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)287 void ExtensionDisabledGlobalError::Observe(
288 int type,
289 const content::NotificationSource& source,
290 const content::NotificationDetails& details) {
291 // The error is invalidated if the extension has been loaded or removed.
292 DCHECK_EQ(NOTIFICATION_EXTENSION_REMOVED, type);
293 const Extension* extension = content::Details<const Extension>(details).ptr();
294 if (extension != extension_)
295 return;
296 RemoveGlobalError();
297 }
298
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)299 void ExtensionDisabledGlobalError::OnExtensionLoaded(
300 content::BrowserContext* browser_context,
301 const Extension* extension) {
302 if (extension != extension_)
303 return;
304 RemoveGlobalError();
305 }
306
OnShutdown(ExtensionRegistry * registry)307 void ExtensionDisabledGlobalError::OnShutdown(ExtensionRegistry* registry) {
308 DCHECK_EQ(ExtensionRegistry::Get(service_->profile()), registry);
309 registry_observer_.RemoveAll();
310 }
311
RemoveGlobalError()312 void ExtensionDisabledGlobalError::RemoveGlobalError() {
313 std::unique_ptr<GlobalError> ptr =
314 GlobalErrorServiceFactory::GetForProfile(service_->profile())
315 ->RemoveGlobalError(this);
316 registrar_.RemoveAll();
317 registry_observer_.RemoveAll();
318 // Delete this object after any running tasks, so that the extension dialog
319 // still has it as a delegate to finish the current tasks.
320 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, ptr.release());
321 }
322
323 // Globals --------------------------------------------------------------------
324
AddExtensionDisabledError(ExtensionService * service,const Extension * extension,bool is_remote_install)325 void AddExtensionDisabledError(ExtensionService* service,
326 const Extension* extension,
327 bool is_remote_install) {
328 if (extension) {
329 GlobalErrorServiceFactory::GetForProfile(service->profile())
330 ->AddGlobalError(std::make_unique<ExtensionDisabledGlobalError>(
331 service, extension, is_remote_install));
332 }
333 }
334
335 } // namespace extensions
336