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/pdf_unsupported_feature.h"
6
7#include "base/utf_string_conversions.h"
8#include "base/values.h"
9#include "base/version.h"
10#include "chrome/browser/metrics/user_metrics.h"
11#include "chrome/browser/prefs/pref_service.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/tab_contents/confirm_infobar_delegate.h"
14#include "chrome/common/chrome_content_client.h"
15#include "chrome/common/jstemplate_builder.h"
16#include "chrome/common/pref_names.h"
17#include "content/browser/plugin_service.h"
18#include "content/browser/renderer_host/render_process_host.h"
19#include "content/browser/renderer_host/render_view_host.h"
20#include "content/browser/tab_contents/interstitial_page.h"
21#include "content/browser/tab_contents/tab_contents.h"
22#include "grit/browser_resources.h"
23#include "grit/generated_resources.h"
24#include "ui/base/l10n/l10n_util.h"
25#include "ui/base/resource/resource_bundle.h"
26#include "webkit/plugins/npapi/plugin_group.h"
27#include "webkit/plugins/npapi/plugin_list.h"
28#include "webkit/plugins/npapi/webplugininfo.h"
29
30using webkit::npapi::PluginGroup;
31using webkit::npapi::PluginList;
32using webkit::npapi::WebPluginInfo;
33
34namespace {
35
36// Only launch Adobe Reader X or later.
37static const uint16 kMinReaderVersionToUse = 10;
38
39static const char kReaderUpdateUrl[] =
40    "http://www.adobe.com/go/getreader_chrome";
41
42// The info bar delegate used to ask the user if they want to use Adobe Reader
43// by default.  We want the infobar to have [No][Yes], so we swap the text on
44// the buttons, and the meaning of the delegate callbacks.
45class PDFEnableAdobeReaderConfirmInfoBarDelegate
46    : public ConfirmInfoBarDelegate {
47 public:
48  PDFEnableAdobeReaderConfirmInfoBarDelegate(
49      TabContents* tab_contents)
50      : ConfirmInfoBarDelegate(tab_contents),
51        tab_contents_(tab_contents) {
52    UserMetrics::RecordAction(
53        UserMetricsAction("PDF_EnableReaderInfoBarShown"));
54  }
55
56  // ConfirmInfoBarDelegate
57  virtual void InfoBarClosed() {
58    delete this;
59  }
60
61  virtual void InfoBarDismissed() {
62    OnNo();
63  }
64
65  virtual Type GetInfoBarType() const {
66    return PAGE_ACTION_TYPE;
67  }
68
69  virtual bool Accept() {
70    tab_contents_->profile()->GetPrefs()->SetBoolean(
71        prefs::kPluginsShowSetReaderDefaultInfobar, false);
72    return OnNo();
73  }
74
75  virtual bool Cancel() {
76    return OnYes();
77  }
78
79  virtual int GetButtons() const {
80    return BUTTON_OK | BUTTON_CANCEL;
81  }
82
83  virtual string16 GetButtonLabel(InfoBarButton button) const {
84    switch (button) {
85      case BUTTON_OK:
86        return l10n_util::GetStringUTF16(
87            IDS_PDF_INFOBAR_NEVER_USE_READER_BUTTON);
88      case BUTTON_CANCEL:
89        return l10n_util::GetStringUTF16(
90            IDS_PDF_INFOBAR_ALWAYS_USE_READER_BUTTON);
91      default:
92        // All buttons are labeled above.
93        NOTREACHED() << "Bad button id " << button;
94        return string16();
95    }
96  }
97
98  virtual string16 GetMessageText() const {
99    return l10n_util::GetStringUTF16(
100        IDS_PDF_INFOBAR_QUESTION_ALWAYS_USE_READER);
101  }
102
103 private:
104  bool OnYes() {
105    UserMetrics::RecordAction(
106        UserMetricsAction("PDF_EnableReaderInfoBarOK"));
107    webkit::npapi::PluginList::Singleton()->EnableGroup(
108        false, ASCIIToUTF16(chrome::ChromeContentClient::kPDFPluginName));
109    webkit::npapi::PluginList::Singleton()->EnableGroup(
110        true, ASCIIToUTF16(webkit::npapi::PluginGroup::kAdobeReaderGroupName));
111    return true;
112  }
113
114  bool OnNo() {
115    UserMetrics::RecordAction(
116        UserMetricsAction("PDF_EnableReaderInfoBarCancel"));
117    return true;
118  }
119
120  TabContents* tab_contents_;
121
122  DISALLOW_IMPLICIT_CONSTRUCTORS(PDFEnableAdobeReaderConfirmInfoBarDelegate);
123};
124
125// Launch the url to get the latest Adbobe Reader installer.
126void OpenReaderUpdateURL(TabContents* tab) {
127  tab->OpenURL(GURL(kReaderUpdateUrl), GURL(), CURRENT_TAB,
128               PageTransition::LINK);
129}
130
131// Opens the PDF using Adobe Reader.
132void OpenUsingReader(TabContents* tab,
133                     const WebPluginInfo& reader_plugin,
134                     InfoBarDelegate* old_delegate,
135                     InfoBarDelegate* new_delegate) {
136  PluginService::OverriddenPlugin plugin;
137  plugin.render_process_id = tab->GetRenderProcessHost()->id();
138  plugin.render_view_id = tab->render_view_host()->routing_id();
139  plugin.url = tab->GetURL();
140  plugin.plugin = reader_plugin;
141  // The plugin is disabled, so enable it to get around the renderer check.
142  // Also give it a new version so that the renderer doesn't show the blocked
143  // plugin UI if it's vulnerable, since we already went through the
144  // interstitial.
145  plugin.plugin.enabled = WebPluginInfo::USER_ENABLED;
146  plugin.plugin.version = ASCIIToUTF16("11.0.0.0");
147
148  PluginService::GetInstance()->OverridePluginForTab(plugin);
149  tab->render_view_host()->ReloadFrame();
150
151  if (new_delegate) {
152    if (old_delegate) {
153      tab->ReplaceInfoBar(old_delegate, new_delegate);
154    } else {
155      tab->AddInfoBar(new_delegate);
156    }
157  }
158}
159
160// An interstitial to be used when the user chooses to open a PDF using Adobe
161// Reader, but it is out of date.
162class PDFUnsupportedFeatureInterstitial : public InterstitialPage {
163 public:
164  PDFUnsupportedFeatureInterstitial(
165      TabContents* tab,
166      const WebPluginInfo& reader_webplugininfo)
167      : InterstitialPage(tab, false, tab->GetURL()),
168        reader_webplugininfo_(reader_webplugininfo) {
169    UserMetrics::RecordAction(UserMetricsAction("PDF_ReaderInterstitialShown"));
170  }
171
172 protected:
173  // InterstitialPage implementation.
174  virtual std::string GetHTMLContents() {
175    DictionaryValue strings;
176    strings.SetString(
177        "title",
178        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_TITLE));
179    strings.SetString(
180        "headLine",
181        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_BODY));
182    strings.SetString(
183        "update",
184        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_UPDATE));
185    strings.SetString(
186        "open_with_reader",
187        l10n_util::GetStringUTF16(
188            IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_PROCEED));
189    strings.SetString(
190        "ok",
191        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_OK));
192    strings.SetString(
193        "cancel",
194        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_CANCEL));
195
196    base::StringPiece html(ResourceBundle::GetSharedInstance().
197        GetRawDataResource(IDR_READER_OUT_OF_DATE_HTML));
198
199    return jstemplate_builder::GetI18nTemplateHtml(html, &strings);
200  }
201
202  virtual void CommandReceived(const std::string& command) {
203    if (command == "0") {
204      UserMetrics::RecordAction(
205          UserMetricsAction("PDF_ReaderInterstitialCancel"));
206      DontProceed();
207      return;
208    }
209
210    if (command == "1") {
211      UserMetrics::RecordAction(
212          UserMetricsAction("PDF_ReaderInterstitialUpdate"));
213      OpenReaderUpdateURL(tab());
214    } else if (command == "2") {
215      UserMetrics::RecordAction(
216          UserMetricsAction("PDF_ReaderInterstitialIgnore"));
217      OpenUsingReader(tab(), reader_webplugininfo_, NULL, NULL);
218    } else {
219      NOTREACHED();
220    }
221    Proceed();
222  }
223
224 private:
225  WebPluginInfo reader_webplugininfo_;
226
227  DISALLOW_COPY_AND_ASSIGN(PDFUnsupportedFeatureInterstitial);
228};
229
230// The info bar delegate used to inform the user that we don't support a feature
231// in the PDF.  See the comment about how we swap buttons for
232// PDFEnableAdobeReaderConfirmInfoBarDelegate.
233class PDFUnsupportedFeatureConfirmInfoBarDelegate
234    : public ConfirmInfoBarDelegate {
235 public:
236  PDFUnsupportedFeatureConfirmInfoBarDelegate(
237      TabContents* tab_contents,
238      PluginGroup* reader_group)  // NULL if Adobe Reader isn't installed.
239      : ConfirmInfoBarDelegate(tab_contents),
240        tab_contents_(tab_contents),
241        reader_installed_(!!reader_group),
242        reader_vulnerable_(false) {
243    if (reader_installed_) {
244      UserMetrics::RecordAction(UserMetricsAction("PDF_UseReaderInfoBarShown"));
245      std::vector<WebPluginInfo> plugins = reader_group->web_plugin_infos();
246      DCHECK_EQ(plugins.size(), 1u);
247      reader_webplugininfo_ = plugins[0];
248
249      reader_vulnerable_ = reader_group->IsVulnerable();
250      if (!reader_vulnerable_) {
251        scoped_ptr<Version> version(PluginGroup::CreateVersionFromString(
252            reader_webplugininfo_.version));
253        if (version.get()) {
254          if (version->components()[0] < kMinReaderVersionToUse)
255            reader_vulnerable_ = true;
256        }
257      }
258    } else {
259      UserMetrics::RecordAction(
260          UserMetricsAction("PDF_InstallReaderInfoBarShown"));
261    }
262  }
263
264  // ConfirmInfoBarDelegate
265  virtual void InfoBarClosed() {
266    delete this;
267  }
268
269  virtual void InfoBarDismissed() {
270    OnNo();
271  }
272
273  virtual Type GetInfoBarType() const {
274    return PAGE_ACTION_TYPE;
275  }
276
277  virtual bool Accept() {
278    return OnNo();
279  }
280
281  virtual bool Cancel() {
282    return OnYes();
283  }
284
285  virtual int GetButtons() const {
286    return BUTTON_OK | BUTTON_CANCEL;
287  }
288
289  virtual string16 GetButtonLabel(InfoBarButton button) const {
290    switch (button) {
291      case BUTTON_OK:
292        return l10n_util::GetStringUTF16(
293            IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL);
294      case BUTTON_CANCEL:
295        return l10n_util::GetStringUTF16(
296            IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL);
297      default:
298        // All buttons are labeled above.
299        NOTREACHED() << "Bad button id " << button;
300        return string16();
301    }
302  }
303
304  virtual string16 GetMessageText() const {
305    return l10n_util::GetStringUTF16(reader_installed_ ?
306        IDS_PDF_INFOBAR_QUESTION_READER_INSTALLED :
307        IDS_PDF_INFOBAR_QUESTION_READER_NOT_INSTALLED);
308  }
309
310 private:
311  bool OnYes() {
312   if (!reader_installed_) {
313      UserMetrics::RecordAction(
314          UserMetricsAction("PDF_InstallReaderInfoBarOK"));
315      OpenReaderUpdateURL(tab_contents_);
316      return true;
317    }
318
319    UserMetrics::RecordAction(
320        UserMetricsAction("PDF_UseReaderInfoBarOK"));
321
322    if (reader_vulnerable_) {
323      PDFUnsupportedFeatureInterstitial* interstitial = new
324          PDFUnsupportedFeatureInterstitial(
325              tab_contents_, reader_webplugininfo_);
326      interstitial->Show();
327      return true;
328    }
329
330    InfoBarDelegate* bar = NULL;
331    if (tab_contents_->profile()->GetPrefs()->GetBoolean(
332            prefs::kPluginsShowSetReaderDefaultInfobar)) {
333      bar = new PDFEnableAdobeReaderConfirmInfoBarDelegate(tab_contents_);
334    }
335
336    if (bar) {
337      OpenUsingReader(tab_contents_, reader_webplugininfo_, this, bar);
338      return false;
339    } else {
340      OpenUsingReader(tab_contents_, reader_webplugininfo_, NULL, NULL);
341      return true;
342    }
343  }
344
345  bool OnNo() {
346    if (reader_installed_) {
347      UserMetrics::RecordAction(
348          UserMetricsAction("PDF_UseReaderInfoBarCancel"));
349    } else {
350      UserMetrics::RecordAction(
351          UserMetricsAction("PDF_InstallReaderInfoBarCancel"));
352    }
353    return true;
354  }
355
356  TabContents* tab_contents_;
357  bool reader_installed_;
358  bool reader_vulnerable_;
359  WebPluginInfo reader_webplugininfo_;
360
361  DISALLOW_IMPLICIT_CONSTRUCTORS(PDFUnsupportedFeatureConfirmInfoBarDelegate);
362};
363
364}  // namespace
365
366void PDFHasUnsupportedFeature(TabContents* tab) {
367#if !defined(OS_WIN)
368  // Only works for Windows for now.  For Mac, we'll have to launch the file
369  // externally since Adobe Reader doesn't work inside Chrome.
370  return;
371#endif
372  string16 reader_group_name(ASCIIToUTF16(PluginGroup::kAdobeReaderGroupName));
373
374  // If the Reader plugin is disabled by policy, don't prompt them.
375  if (PluginGroup::IsPluginNameDisabledByPolicy(reader_group_name))
376    return;
377
378  PluginGroup* reader_group = NULL;
379  std::vector<PluginGroup> plugin_groups;
380  PluginList::Singleton()->GetPluginGroups(
381      false, &plugin_groups);
382  for (size_t i = 0; i < plugin_groups.size(); ++i) {
383    if (plugin_groups[i].GetGroupName() == reader_group_name) {
384      reader_group = &plugin_groups[i];
385      break;
386    }
387  }
388
389  tab->AddInfoBar(new PDFUnsupportedFeatureConfirmInfoBarDelegate(
390      tab, reader_group));
391}
392