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