1// 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
7#include "base/bind.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/app/chrome_command_ids.h"
10#include "chrome/browser/extensions/extension_service.h"
11#include "chrome/browser/extensions/external_install_manager.h"
12#include "chrome/browser/extensions/webstore_data_fetcher.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_finder.h"
16#include "chrome/browser/ui/global_error/global_error.h"
17#include "chrome/browser/ui/global_error/global_error_service.h"
18#include "chrome/browser/ui/global_error/global_error_service_factory.h"
19#include "chrome/browser/ui/tabs/tab_strip_model.h"
20#include "chrome/grit/generated_resources.h"
21#include "extensions/browser/extension_registry.h"
22#include "extensions/browser/extension_system.h"
23#include "extensions/browser/uninstall_reason.h"
24#include "extensions/common/constants.h"
25#include "extensions/common/extension.h"
26#include "ui/base/l10n/l10n_util.h"
27#include "ui/gfx/image/image.h"
28#include "ui/gfx/image/image_skia_operations.h"
29
30namespace extensions {
31
32namespace {
33
34// Return the menu label for a global error.
35base::string16 GetMenuItemLabel(const Extension* extension) {
36  if (!extension)
37    return base::string16();
38
39  int id = -1;
40  if (extension->is_app())
41    id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_APP;
42  else if (extension->is_theme())
43    id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_THEME;
44  else
45    id = IDS_EXTENSION_EXTERNAL_INSTALL_ALERT_EXTENSION;
46
47  return l10n_util::GetStringFUTF16(id, base::UTF8ToUTF16(extension->name()));
48}
49
50// A global error that spawns a dialog when the menu item is clicked.
51class ExternalInstallMenuAlert : public GlobalError {
52 public:
53  explicit ExternalInstallMenuAlert(ExternalInstallError* error);
54  virtual ~ExternalInstallMenuAlert();
55
56 private:
57  // GlobalError implementation.
58  virtual Severity GetSeverity() OVERRIDE;
59  virtual bool HasMenuItem() OVERRIDE;
60  virtual int MenuItemCommandID() OVERRIDE;
61  virtual base::string16 MenuItemLabel() OVERRIDE;
62  virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
63  virtual bool HasBubbleView() OVERRIDE;
64  virtual bool HasShownBubbleView() OVERRIDE;
65  virtual void ShowBubbleView(Browser* browser) OVERRIDE;
66  virtual GlobalErrorBubbleViewBase* GetBubbleView() OVERRIDE;
67
68  // The owning ExternalInstallError.
69  ExternalInstallError* error_;
70
71  DISALLOW_COPY_AND_ASSIGN(ExternalInstallMenuAlert);
72};
73
74// A global error that spawns a bubble when the menu item is clicked.
75class ExternalInstallBubbleAlert : public GlobalErrorWithStandardBubble {
76 public:
77  explicit ExternalInstallBubbleAlert(ExternalInstallError* error,
78                                      ExtensionInstallPrompt::Prompt* prompt);
79  virtual ~ExternalInstallBubbleAlert();
80
81 private:
82  // GlobalError implementation.
83  virtual Severity GetSeverity() OVERRIDE;
84  virtual bool HasMenuItem() OVERRIDE;
85  virtual int MenuItemCommandID() OVERRIDE;
86  virtual base::string16 MenuItemLabel() OVERRIDE;
87  virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
88
89  // GlobalErrorWithStandardBubble implementation.
90  virtual gfx::Image GetBubbleViewIcon() OVERRIDE;
91  virtual base::string16 GetBubbleViewTitle() OVERRIDE;
92  virtual std::vector<base::string16> GetBubbleViewMessages() OVERRIDE;
93  virtual base::string16 GetBubbleViewAcceptButtonLabel() OVERRIDE;
94  virtual base::string16 GetBubbleViewCancelButtonLabel() OVERRIDE;
95  virtual void OnBubbleViewDidClose(Browser* browser) OVERRIDE;
96  virtual void BubbleViewAcceptButtonPressed(Browser* browser) OVERRIDE;
97  virtual void BubbleViewCancelButtonPressed(Browser* browser) OVERRIDE;
98
99  // The owning ExternalInstallError.
100  ExternalInstallError* error_;
101
102  // The Prompt with all information, which we then use to populate the bubble.
103  ExtensionInstallPrompt::Prompt* prompt_;
104
105  DISALLOW_COPY_AND_ASSIGN(ExternalInstallBubbleAlert);
106};
107
108////////////////////////////////////////////////////////////////////////////////
109// ExternalInstallMenuAlert
110
111ExternalInstallMenuAlert::ExternalInstallMenuAlert(ExternalInstallError* error)
112    : error_(error) {
113}
114
115ExternalInstallMenuAlert::~ExternalInstallMenuAlert() {
116}
117
118GlobalError::Severity ExternalInstallMenuAlert::GetSeverity() {
119  return SEVERITY_LOW;
120}
121
122bool ExternalInstallMenuAlert::HasMenuItem() {
123  return true;
124}
125
126int ExternalInstallMenuAlert::MenuItemCommandID() {
127  return IDC_EXTERNAL_EXTENSION_ALERT;
128}
129
130base::string16 ExternalInstallMenuAlert::MenuItemLabel() {
131  return GetMenuItemLabel(error_->GetExtension());
132}
133
134void ExternalInstallMenuAlert::ExecuteMenuItem(Browser* browser) {
135  error_->ShowDialog(browser);
136}
137
138bool ExternalInstallMenuAlert::HasBubbleView() {
139  return false;
140}
141
142bool ExternalInstallMenuAlert::HasShownBubbleView() {
143  NOTREACHED();
144  return true;
145}
146
147void ExternalInstallMenuAlert::ShowBubbleView(Browser* browser) {
148  NOTREACHED();
149}
150
151GlobalErrorBubbleViewBase* ExternalInstallMenuAlert::GetBubbleView() {
152  return NULL;
153}
154
155////////////////////////////////////////////////////////////////////////////////
156// ExternalInstallBubbleAlert
157
158ExternalInstallBubbleAlert::ExternalInstallBubbleAlert(
159    ExternalInstallError* error,
160    ExtensionInstallPrompt::Prompt* prompt)
161    : error_(error), prompt_(prompt) {
162  DCHECK(error_);
163  DCHECK(prompt_);
164}
165
166ExternalInstallBubbleAlert::~ExternalInstallBubbleAlert() {
167}
168
169GlobalError::Severity ExternalInstallBubbleAlert::GetSeverity() {
170  return SEVERITY_LOW;
171}
172
173bool ExternalInstallBubbleAlert::HasMenuItem() {
174  return true;
175}
176
177int ExternalInstallBubbleAlert::MenuItemCommandID() {
178  return IDC_EXTERNAL_EXTENSION_ALERT;
179}
180
181base::string16 ExternalInstallBubbleAlert::MenuItemLabel() {
182  return GetMenuItemLabel(error_->GetExtension());
183}
184
185void ExternalInstallBubbleAlert::ExecuteMenuItem(Browser* browser) {
186  ShowBubbleView(browser);
187}
188
189gfx::Image ExternalInstallBubbleAlert::GetBubbleViewIcon() {
190  if (prompt_->icon().IsEmpty())
191    return GlobalErrorWithStandardBubble::GetBubbleViewIcon();
192  // Scale icon to a reasonable size.
193  return gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
194      *prompt_->icon().ToImageSkia(),
195      skia::ImageOperations::RESIZE_BEST,
196      gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
197                extension_misc::EXTENSION_ICON_SMALL)));
198}
199
200base::string16 ExternalInstallBubbleAlert::GetBubbleViewTitle() {
201  return prompt_->GetDialogTitle();
202}
203
204std::vector<base::string16>
205ExternalInstallBubbleAlert::GetBubbleViewMessages() {
206  ExtensionInstallPrompt::PermissionsType regular_permissions =
207      ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS;
208  ExtensionInstallPrompt::PermissionsType withheld_permissions =
209      ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS;
210
211  std::vector<base::string16> messages;
212  messages.push_back(prompt_->GetHeading());
213  if (prompt_->GetPermissionCount(regular_permissions)) {
214    messages.push_back(prompt_->GetPermissionsHeading(regular_permissions));
215    for (size_t i = 0; i < prompt_->GetPermissionCount(regular_permissions);
216         ++i) {
217      messages.push_back(l10n_util::GetStringFUTF16(
218          IDS_EXTENSION_PERMISSION_LINE,
219          prompt_->GetPermission(i, regular_permissions)));
220    }
221  }
222  if (prompt_->GetPermissionCount(withheld_permissions)) {
223    messages.push_back(prompt_->GetPermissionsHeading(withheld_permissions));
224    for (size_t i = 0; i < prompt_->GetPermissionCount(withheld_permissions);
225         ++i) {
226      messages.push_back(l10n_util::GetStringFUTF16(
227          IDS_EXTENSION_PERMISSION_LINE,
228          prompt_->GetPermission(i, withheld_permissions)));
229    }
230  }
231  // TODO(yoz): OAuth issue advice?
232  return messages;
233}
234
235base::string16 ExternalInstallBubbleAlert::GetBubbleViewAcceptButtonLabel() {
236  return prompt_->GetAcceptButtonLabel();
237}
238
239base::string16 ExternalInstallBubbleAlert::GetBubbleViewCancelButtonLabel() {
240  return prompt_->GetAbortButtonLabel();
241}
242
243void ExternalInstallBubbleAlert::OnBubbleViewDidClose(Browser* browser) {
244}
245
246void ExternalInstallBubbleAlert::BubbleViewAcceptButtonPressed(
247    Browser* browser) {
248  error_->InstallUIProceed();
249}
250
251void ExternalInstallBubbleAlert::BubbleViewCancelButtonPressed(
252    Browser* browser) {
253  error_->InstallUIAbort(true);
254}
255
256}  // namespace
257
258////////////////////////////////////////////////////////////////////////////////
259// ExternalInstallError
260
261ExternalInstallError::ExternalInstallError(
262    content::BrowserContext* browser_context,
263    const std::string& extension_id,
264    AlertType alert_type,
265    ExternalInstallManager* manager)
266    : browser_context_(browser_context),
267      extension_id_(extension_id),
268      alert_type_(alert_type),
269      manager_(manager),
270      error_service_(GlobalErrorServiceFactory::GetForProfile(
271          Profile::FromBrowserContext(browser_context_))),
272      weak_factory_(this) {
273  prompt_ = new ExtensionInstallPrompt::Prompt(
274      ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT);
275
276  webstore_data_fetcher_.reset(new WebstoreDataFetcher(
277      this, browser_context_->GetRequestContext(), GURL(), extension_id_));
278  webstore_data_fetcher_->Start();
279}
280
281ExternalInstallError::~ExternalInstallError() {
282  if (global_error_.get())
283    error_service_->RemoveGlobalError(global_error_.get());
284}
285
286void ExternalInstallError::InstallUIProceed() {
287  const Extension* extension = GetExtension();
288  if (extension) {
289    ExtensionSystem::Get(browser_context_)
290        ->extension_service()
291        ->GrantPermissionsAndEnableExtension(extension);
292    // Since the manager listens for the extension to be loaded, this will
293    // remove the error...
294  } else {
295    // ... Otherwise we have to do it explicitly.
296    manager_->RemoveExternalInstallError();
297  }
298}
299
300void ExternalInstallError::InstallUIAbort(bool user_initiated) {
301  if (user_initiated && GetExtension()) {
302    ExtensionSystem::Get(browser_context_)
303        ->extension_service()
304        ->UninstallExtension(extension_id_,
305                             extensions::UNINSTALL_REASON_INSTALL_CANCELED,
306                             base::Bind(&base::DoNothing),
307                             NULL);  // Ignore error.
308    // Since the manager listens for the extension to be removed, this will
309    // remove the error...
310  } else {
311    // ... Otherwise we have to do it explicitly.
312    manager_->RemoveExternalInstallError();
313  }
314}
315
316void ExternalInstallError::ShowDialog(Browser* browser) {
317  DCHECK(install_ui_.get());
318  DCHECK(prompt_.get());
319  DCHECK(browser);
320  content::WebContents* web_contents = NULL;
321  web_contents = browser->tab_strip_model()->GetActiveWebContents();
322  ExtensionInstallPrompt::ShowParams params(web_contents);
323  ExtensionInstallPrompt::GetDefaultShowDialogCallback().Run(
324      params, this, prompt_);
325}
326
327const Extension* ExternalInstallError::GetExtension() const {
328  return ExtensionRegistry::Get(browser_context_)
329      ->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
330}
331
332void ExternalInstallError::OnWebstoreRequestFailure() {
333  OnFetchComplete();
334}
335
336void ExternalInstallError::OnWebstoreResponseParseSuccess(
337    scoped_ptr<base::DictionaryValue> webstore_data) {
338  std::string localized_user_count;
339  double average_rating = 0;
340  int rating_count = 0;
341  if (!webstore_data->GetString(kUsersKey, &localized_user_count) ||
342      !webstore_data->GetDouble(kAverageRatingKey, &average_rating) ||
343      !webstore_data->GetInteger(kRatingCountKey, &rating_count)) {
344    // If we don't get a valid webstore response, short circuit, and continue
345    // to show a prompt without webstore data.
346    OnFetchComplete();
347    return;
348  }
349
350  bool show_user_count = true;
351  webstore_data->GetBoolean(kShowUserCountKey, &show_user_count);
352
353  prompt_->SetWebstoreData(
354      localized_user_count, show_user_count, average_rating, rating_count);
355  OnFetchComplete();
356}
357
358void ExternalInstallError::OnWebstoreResponseParseFailure(
359    const std::string& error) {
360  OnFetchComplete();
361}
362
363void ExternalInstallError::OnFetchComplete() {
364  // Create a new ExtensionInstallPrompt. We pass in NULL for the UI
365  // components because we display at a later point, and don't want
366  // to pass ones which may be invalidated.
367  install_ui_.reset(
368      new ExtensionInstallPrompt(Profile::FromBrowserContext(browser_context_),
369                                 NULL,    // NULL native window.
370                                 NULL));  // NULL navigator.
371
372  install_ui_->ConfirmExternalInstall(
373      this,
374      GetExtension(),
375      base::Bind(&ExternalInstallError::OnDialogReady,
376                 weak_factory_.GetWeakPtr()),
377      prompt_);
378}
379
380void ExternalInstallError::OnDialogReady(
381    const ExtensionInstallPrompt::ShowParams& show_params,
382    ExtensionInstallPrompt::Delegate* prompt_delegate,
383    scoped_refptr<ExtensionInstallPrompt::Prompt> prompt) {
384  DCHECK_EQ(this, prompt_delegate);
385  prompt_ = prompt;
386
387  if (alert_type_ == BUBBLE_ALERT) {
388    global_error_.reset(new ExternalInstallBubbleAlert(this, prompt_.get()));
389    error_service_->AddGlobalError(global_error_.get());
390
391    Browser* browser =
392        chrome::FindTabbedBrowser(Profile::FromBrowserContext(browser_context_),
393                                  true,
394                                  chrome::GetActiveDesktop());
395    if (browser)
396      global_error_->ShowBubbleView(browser);
397  } else {
398    DCHECK(alert_type_ == MENU_ALERT);
399    global_error_.reset(new ExternalInstallMenuAlert(this));
400    error_service_->AddGlobalError(global_error_.get());
401  }
402}
403
404}  // namespace extensions
405