1// Copyright (c) 2011 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_install_ui.h"
6
7#include <map>
8
9#include "base/command_line.h"
10#include "base/file_util.h"
11#include "base/string_number_conversions.h"
12#include "base/string_util.h"
13#include "base/stringprintf.h"
14#include "base/utf_string_conversions.h"
15#include "chrome/browser/extensions/extension_install_dialog.h"
16#include "chrome/browser/extensions/theme_installed_infobar_delegate.h"
17#include "chrome/browser/platform_util.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/browser/tabs/tab_strip_model.h"
20#include "chrome/browser/themes/theme_service_factory.h"
21#include "chrome/browser/ui/browser.h"
22#include "chrome/browser/ui/browser_dialogs.h"
23#include "chrome/browser/ui/browser_list.h"
24#include "chrome/browser/ui/browser_window.h"
25#include "chrome/common/extensions/extension.h"
26#include "chrome/common/extensions/extension_icon_set.h"
27#include "chrome/common/extensions/extension_resource.h"
28#include "chrome/common/extensions/url_pattern.h"
29#include "chrome/common/url_constants.h"
30#include "content/browser/tab_contents/tab_contents.h"
31#include "content/common/notification_service.h"
32#include "grit/chromium_strings.h"
33#include "grit/generated_resources.h"
34#include "grit/theme_resources.h"
35#include "ui/base/l10n/l10n_util.h"
36
37#if defined(TOOLKIT_GTK)
38#include "chrome/browser/extensions/gtk_theme_installed_infobar_delegate.h"
39#include "chrome/browser/ui/gtk/gtk_theme_service.h"
40#endif
41
42// static
43const int ExtensionInstallUI::kTitleIds[NUM_PROMPT_TYPES] = {
44  IDS_EXTENSION_INSTALL_PROMPT_TITLE,
45  IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE
46};
47// static
48const int ExtensionInstallUI::kHeadingIds[NUM_PROMPT_TYPES] = {
49  IDS_EXTENSION_INSTALL_PROMPT_HEADING,
50  IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING
51};
52// static
53const int ExtensionInstallUI::kButtonIds[NUM_PROMPT_TYPES] = {
54  IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
55  IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON
56};
57// static
58const int ExtensionInstallUI::kWarningIds[NUM_PROMPT_TYPES] = {
59  IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
60  IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO
61};
62
63namespace {
64
65// Size of extension icon in top left of dialog.
66const int kIconSize = 69;
67
68// Shows the application install animation on the new tab page for the app
69// with |app_id|. If a NTP already exists on the active |browser|, this will
70// select that tab and show the animation there. Otherwise, it will create
71// a new NTP.
72void ShowAppInstalledAnimation(Browser* browser, const std::string& app_id) {
73  // Select an already open NTP, if there is one. Existing NTPs will
74  // automatically show the install animation for any new apps.
75  for (int i = 0; i < browser->tab_count(); ++i) {
76    TabContents* tab_contents = browser->GetTabContentsAt(i);
77    GURL url = tab_contents->GetURL();
78    if (StartsWithASCII(url.spec(), chrome::kChromeUINewTabURL, false)) {
79      browser->ActivateTabAt(i, false);
80      return;
81    }
82  }
83
84  // If there isn't an NTP, open one and pass it the ID of the installed app.
85  std::string url = base::StringPrintf(
86      "%s/#app-id=%s", chrome::kChromeUINewTabURL, app_id.c_str());
87  browser->AddSelectedTabWithURL(GURL(url), PageTransition::TYPED);
88}
89
90}  // namespace
91
92ExtensionInstallUI::ExtensionInstallUI(Profile* profile)
93    : profile_(profile),
94      ui_loop_(MessageLoop::current()),
95      previous_use_system_theme_(false),
96      extension_(NULL),
97      delegate_(NULL),
98      prompt_type_(NUM_PROMPT_TYPES),
99      ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) {
100  // Remember the current theme in case the user presses undo.
101  if (profile_) {
102    const Extension* previous_theme =
103        ThemeServiceFactory::GetThemeForProfile(profile_);
104    if (previous_theme)
105      previous_theme_id_ = previous_theme->id();
106#if defined(TOOLKIT_GTK)
107    // On Linux, we also need to take the user's system settings into account
108    // to undo theme installation.
109    previous_use_system_theme_ =
110        GtkThemeService::GetFrom(profile_)->UseGtkTheme();
111#else
112    DCHECK(!previous_use_system_theme_);
113#endif
114  }
115}
116
117ExtensionInstallUI::~ExtensionInstallUI() {
118}
119
120void ExtensionInstallUI::ConfirmInstall(Delegate* delegate,
121                                        const Extension* extension) {
122  DCHECK(ui_loop_ == MessageLoop::current());
123  extension_ = extension;
124  delegate_ = delegate;
125
126  // We special-case themes to not show any confirm UI. Instead they are
127  // immediately installed, and then we show an infobar (see OnInstallSuccess)
128  // to allow the user to revert if they don't like it.
129  if (extension->is_theme()) {
130    delegate->InstallUIProceed();
131    return;
132  }
133
134  ShowConfirmation(INSTALL_PROMPT);
135}
136
137void ExtensionInstallUI::ConfirmReEnable(Delegate* delegate,
138                                         const Extension* extension) {
139  DCHECK(ui_loop_ == MessageLoop::current());
140  extension_ = extension;
141  delegate_ = delegate;
142
143  ShowConfirmation(RE_ENABLE_PROMPT);
144}
145
146void ExtensionInstallUI::OnInstallSuccess(const Extension* extension,
147                                          SkBitmap* icon) {
148  extension_ = extension;
149  SetIcon(icon);
150
151  if (extension->is_theme()) {
152    ShowThemeInfoBar(previous_theme_id_, previous_use_system_theme_,
153                     extension, profile_);
154    return;
155  }
156
157  // Extensions aren't enabled by default in incognito so we confirm
158  // the install in a normal window.
159  Profile* profile = profile_->GetOriginalProfile();
160  Browser* browser = Browser::GetOrCreateTabbedBrowser(profile);
161  if (browser->tab_count() == 0)
162    browser->AddBlankTab(true);
163  browser->window()->Show();
164
165  if (extension->GetFullLaunchURL().is_valid()) {
166    ShowAppInstalledAnimation(browser, extension->id());
167    return;
168  }
169
170  browser::ShowExtensionInstalledBubble(extension, browser, icon_, profile);
171}
172
173void ExtensionInstallUI::OnInstallFailure(const std::string& error) {
174  DCHECK(ui_loop_ == MessageLoop::current());
175
176  Browser* browser = BrowserList::GetLastActiveWithProfile(profile_);
177  platform_util::SimpleErrorBox(
178      browser ? browser->window()->GetNativeHandle() : NULL,
179      l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_FAILURE_TITLE),
180      UTF8ToUTF16(error));
181}
182
183void ExtensionInstallUI::SetIcon(SkBitmap* image) {
184  if (image)
185    icon_ = *image;
186  else
187    icon_ = SkBitmap();
188  if (icon_.empty())
189    icon_ = Extension::GetDefaultIcon(extension_->is_app());
190}
191
192void ExtensionInstallUI::OnImageLoaded(
193    SkBitmap* image, const ExtensionResource& resource, int index) {
194  SetIcon(image);
195
196  switch (prompt_type_) {
197    case RE_ENABLE_PROMPT:
198    case INSTALL_PROMPT: {
199      // TODO(jcivelli): http://crbug.com/44771 We should not show an install
200      //                 dialog when installing an app from the gallery.
201      NotificationService* service = NotificationService::current();
202      service->Notify(NotificationType::EXTENSION_WILL_SHOW_CONFIRM_DIALOG,
203          Source<ExtensionInstallUI>(this),
204          NotificationService::NoDetails());
205
206      std::vector<string16> warnings =
207          extension_->GetPermissionMessageStrings();
208      ShowExtensionInstallDialog(
209          profile_, delegate_, extension_, &icon_, warnings, prompt_type_);
210      break;
211    }
212    default:
213      NOTREACHED() << "Unknown message";
214      break;
215  }
216}
217
218void ExtensionInstallUI::ShowThemeInfoBar(const std::string& previous_theme_id,
219                                          bool previous_use_system_theme,
220                                          const Extension* new_theme,
221                                          Profile* profile) {
222  if (!new_theme->is_theme())
223    return;
224
225  // Get last active normal browser of profile.
226  Browser* browser = BrowserList::FindBrowserWithType(profile,
227                                                      Browser::TYPE_NORMAL,
228                                                      true);
229  if (!browser)
230    return;
231
232  TabContents* tab_contents = browser->GetSelectedTabContents();
233  if (!tab_contents)
234    return;
235
236  // First find any previous theme preview infobars.
237  InfoBarDelegate* old_delegate = NULL;
238  for (size_t i = 0; i < tab_contents->infobar_count(); ++i) {
239    InfoBarDelegate* delegate = tab_contents->GetInfoBarDelegateAt(i);
240    ThemeInstalledInfoBarDelegate* theme_infobar =
241        delegate->AsThemePreviewInfobarDelegate();
242    if (theme_infobar) {
243      // If the user installed the same theme twice, ignore the second install
244      // and keep the first install info bar, so that they can easily undo to
245      // get back the previous theme.
246      if (theme_infobar->MatchesTheme(new_theme))
247        return;
248      old_delegate = delegate;
249      break;
250    }
251  }
252
253  // Then either replace that old one or add a new one.
254  InfoBarDelegate* new_delegate = GetNewThemeInstalledInfoBarDelegate(
255      tab_contents, new_theme, previous_theme_id, previous_use_system_theme);
256
257  if (old_delegate)
258    tab_contents->ReplaceInfoBar(old_delegate, new_delegate);
259  else
260    tab_contents->AddInfoBar(new_delegate);
261}
262
263void ExtensionInstallUI::ShowConfirmation(PromptType prompt_type) {
264  // Load the image asynchronously. For the response, check OnImageLoaded.
265  prompt_type_ = prompt_type;
266  ExtensionResource image =
267      extension_->GetIconResource(Extension::EXTENSION_ICON_LARGE,
268                                  ExtensionIconSet::MATCH_EXACTLY);
269  tracker_.LoadImage(extension_, image,
270                     gfx::Size(kIconSize, kIconSize),
271                     ImageLoadingTracker::DONT_CACHE);
272}
273
274InfoBarDelegate* ExtensionInstallUI::GetNewThemeInstalledInfoBarDelegate(
275    TabContents* tab_contents,
276    const Extension* new_theme,
277    const std::string& previous_theme_id,
278    bool previous_use_system_theme) {
279#if defined(TOOLKIT_GTK)
280  return new GtkThemeInstalledInfoBarDelegate(tab_contents, new_theme,
281      previous_theme_id, previous_use_system_theme);
282#else
283  return new ThemeInstalledInfoBarDelegate(tab_contents, new_theme,
284                                           previous_theme_id);
285#endif
286}
287