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/ui/pdf/pdf_unsupported_feature.h"
6
7#include "base/bind.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/prefs/pref_service.h"
10#include "base/strings/utf_string_conversions.h"
11#include "base/values.h"
12#include "base/version.h"
13#include "chrome/browser/lifetime/application_lifetime.h"
14#include "chrome/browser/plugins/chrome_plugin_service_filter.h"
15#include "chrome/browser/plugins/plugin_finder.h"
16#include "chrome/browser/plugins/plugin_metadata.h"
17#include "chrome/browser/plugins/plugin_prefs.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/browser/renderer_preferences_util.h"
20#include "chrome/browser/tab_contents/tab_util.h"
21#include "chrome/browser/ui/pdf/open_pdf_in_reader_prompt_delegate.h"
22#include "chrome/browser/ui/pdf/pdf_tab_helper.h"
23#include "chrome/common/chrome_content_client.h"
24#include "chrome/common/pref_names.h"
25#include "content/public/browser/interstitial_page.h"
26#include "content/public/browser/interstitial_page_delegate.h"
27#include "content/public/browser/navigation_details.h"
28#include "content/public/browser/navigation_entry.h"
29#include "content/public/browser/plugin_service.h"
30#include "content/public/browser/render_process_host.h"
31#include "content/public/browser/render_view_host.h"
32#include "content/public/browser/user_metrics.h"
33#include "content/public/browser/web_contents.h"
34#include "content/public/common/page_transition_types.h"
35#include "grit/browser_resources.h"
36#include "grit/generated_resources.h"
37#include "grit/theme_resources.h"
38#include "ui/base/l10n/l10n_util.h"
39#include "ui/base/resource/resource_bundle.h"
40#include "ui/gfx/image/image.h"
41#include "ui/webui/jstemplate_builder.h"
42
43#if defined(OS_WIN)
44#include "base/win/metro.h"
45#endif
46
47using content::InterstitialPage;
48using content::OpenURLParams;
49using content::PluginService;
50using content::Referrer;
51using content::UserMetricsAction;
52using content::WebContents;
53using content::WebPluginInfo;
54
55namespace {
56
57static const char kAdobeReaderIdentifier[] = "adobe-reader";
58static const char kAdobeReaderUpdateUrl[] =
59    "http://www.adobe.com/go/getreader_chrome";
60
61// The prompt delegate used to ask the user if they want to use Adobe Reader
62// by default.
63class PDFEnableAdobeReaderPromptDelegate
64    : public OpenPDFInReaderPromptDelegate {
65 public:
66  explicit PDFEnableAdobeReaderPromptDelegate(Profile* profile);
67  virtual ~PDFEnableAdobeReaderPromptDelegate();
68
69  // OpenPDFInReaderPromptDelegate
70  virtual string16 GetMessageText() const OVERRIDE;
71  virtual string16 GetAcceptButtonText() const OVERRIDE;
72  virtual string16 GetCancelButtonText() const OVERRIDE;
73  virtual bool ShouldExpire(
74      const content::LoadCommittedDetails& details) const OVERRIDE;
75  virtual void Accept() OVERRIDE;
76  virtual void Cancel() OVERRIDE;
77
78 private:
79  void OnYes();
80  void OnNo();
81
82  Profile* profile_;
83
84  DISALLOW_IMPLICIT_CONSTRUCTORS(PDFEnableAdobeReaderPromptDelegate);
85};
86
87PDFEnableAdobeReaderPromptDelegate::PDFEnableAdobeReaderPromptDelegate(
88    Profile* profile)
89    : profile_(profile) {
90  content::RecordAction(UserMetricsAction("PDF_EnableReaderInfoBarShown"));
91}
92
93PDFEnableAdobeReaderPromptDelegate::~PDFEnableAdobeReaderPromptDelegate() {
94}
95
96bool PDFEnableAdobeReaderPromptDelegate::ShouldExpire(
97    const content::LoadCommittedDetails& details) const {
98  content::PageTransition transition =
99      content::PageTransitionStripQualifier(details.entry->GetTransitionType());
100  // We don't want to expire on a reload, because that is how we open the PDF in
101  // Reader.
102  return !details.is_in_page && transition != content::PAGE_TRANSITION_RELOAD;
103}
104
105void PDFEnableAdobeReaderPromptDelegate::Accept() {
106  content::RecordAction(UserMetricsAction("PDF_EnableReaderInfoBarOK"));
107  PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get();
108  plugin_prefs->EnablePluginGroup(
109      true, ASCIIToUTF16(PluginMetadata::kAdobeReaderGroupName));
110  plugin_prefs->EnablePluginGroup(
111      false, ASCIIToUTF16(chrome::ChromeContentClient::kPDFPluginName));
112}
113
114void PDFEnableAdobeReaderPromptDelegate::Cancel() {
115  content::RecordAction(UserMetricsAction("PDF_EnableReaderInfoBarCancel"));
116}
117
118string16 PDFEnableAdobeReaderPromptDelegate::GetAcceptButtonText() const {
119  return l10n_util::GetStringUTF16(IDS_PDF_INFOBAR_ALWAYS_USE_READER_BUTTON);
120}
121
122string16 PDFEnableAdobeReaderPromptDelegate::GetCancelButtonText() const {
123  return l10n_util::GetStringUTF16(IDS_DONE);
124}
125
126string16 PDFEnableAdobeReaderPromptDelegate::GetMessageText() const {
127  return l10n_util::GetStringUTF16(IDS_PDF_INFOBAR_QUESTION_ALWAYS_USE_READER);
128}
129
130// Launch the url to get the latest Adbobe Reader installer.
131void OpenReaderUpdateURL(WebContents* web_contents) {
132  OpenURLParams params(
133      GURL(kAdobeReaderUpdateUrl), Referrer(), NEW_FOREGROUND_TAB,
134      content::PAGE_TRANSITION_LINK, false);
135  web_contents->OpenURL(params);
136}
137
138// Opens the PDF using Adobe Reader.
139void OpenUsingReader(WebContents* web_contents,
140                     const WebPluginInfo& reader_plugin,
141                     OpenPDFInReaderPromptDelegate* delegate) {
142  ChromePluginServiceFilter::GetInstance()->OverridePluginForTab(
143      web_contents->GetRenderProcessHost()->GetID(),
144      web_contents->GetRenderViewHost()->GetRoutingID(),
145      web_contents->GetURL(),
146      reader_plugin);
147  web_contents->GetRenderViewHost()->ReloadFrame();
148
149  PDFTabHelper* pdf_tab_helper = PDFTabHelper::FromWebContents(web_contents);
150  if (delegate)
151    pdf_tab_helper->ShowOpenInReaderPrompt(make_scoped_ptr(delegate));
152}
153
154// An interstitial to be used when the user chooses to open a PDF using Adobe
155// Reader, but it is out of date.
156class PDFUnsupportedFeatureInterstitial
157    : public content::InterstitialPageDelegate {
158 public:
159  PDFUnsupportedFeatureInterstitial(
160      WebContents* web_contents,
161      const WebPluginInfo& reader_webplugininfo)
162      : web_contents_(web_contents),
163        reader_webplugininfo_(reader_webplugininfo) {
164    content::RecordAction(UserMetricsAction("PDF_ReaderInterstitialShown"));
165    interstitial_page_ = InterstitialPage::Create(
166        web_contents, false, web_contents->GetURL(), this);
167    interstitial_page_->Show();
168  }
169
170 protected:
171  // InterstitialPageDelegate implementation.
172  virtual std::string GetHTMLContents() OVERRIDE {
173    DictionaryValue strings;
174    strings.SetString(
175        "title",
176        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_TITLE));
177    strings.SetString(
178        "headLine",
179        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_BODY));
180    strings.SetString(
181        "update",
182        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_UPDATE));
183    strings.SetString(
184        "open_with_reader",
185        l10n_util::GetStringUTF16(
186            IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_PROCEED));
187    strings.SetString(
188        "ok",
189        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_OK));
190    strings.SetString(
191        "cancel",
192        l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_CANCEL));
193
194    base::StringPiece html(ResourceBundle::GetSharedInstance().
195                           GetRawDataResource(IDR_READER_OUT_OF_DATE_HTML));
196
197    return webui::GetI18nTemplateHtml(html, &strings);
198  }
199
200  virtual void CommandReceived(const std::string& command) OVERRIDE {
201    if (command == "0") {
202      content::RecordAction(
203          UserMetricsAction("PDF_ReaderInterstitialCancel"));
204      interstitial_page_->DontProceed();
205      return;
206    }
207
208    if (command == "1") {
209      content::RecordAction(
210          UserMetricsAction("PDF_ReaderInterstitialUpdate"));
211      OpenReaderUpdateURL(web_contents_);
212    } else if (command == "2") {
213      content::RecordAction(
214          UserMetricsAction("PDF_ReaderInterstitialIgnore"));
215      // Pretend that the plug-in is up-to-date so that we don't block it.
216      reader_webplugininfo_.version = ASCIIToUTF16("11.0.0.0");
217      OpenUsingReader(web_contents_, reader_webplugininfo_, NULL);
218    } else {
219      NOTREACHED();
220    }
221    interstitial_page_->Proceed();
222  }
223
224  virtual void OverrideRendererPrefs(
225      content::RendererPreferences* prefs) OVERRIDE {
226    Profile* profile =
227        Profile::FromBrowserContext(web_contents_->GetBrowserContext());
228    renderer_preferences_util::UpdateFromSystemSettings(prefs, profile);
229  }
230
231 private:
232  WebContents* web_contents_;
233  WebPluginInfo reader_webplugininfo_;
234  InterstitialPage* interstitial_page_;  // Owns us.
235
236  DISALLOW_COPY_AND_ASSIGN(PDFUnsupportedFeatureInterstitial);
237};
238
239// The delegate for the bubble used to inform the user that we don't support a
240// feature in the PDF.
241class PDFUnsupportedFeaturePromptDelegate
242    : public OpenPDFInReaderPromptDelegate {
243 public:
244  // |reader| is NULL if Adobe Reader isn't installed.
245  PDFUnsupportedFeaturePromptDelegate(WebContents* web_contents,
246                                      const content::WebPluginInfo* reader,
247                                      PluginFinder* plugin_finder);
248  virtual ~PDFUnsupportedFeaturePromptDelegate();
249
250  // OpenPDFInReaderPromptDelegate:
251  virtual string16 GetMessageText() const OVERRIDE;
252  virtual string16 GetAcceptButtonText() const OVERRIDE;
253  virtual string16 GetCancelButtonText() const OVERRIDE;
254  virtual bool ShouldExpire(
255      const content::LoadCommittedDetails& details) const OVERRIDE;
256  virtual void Accept() OVERRIDE;
257  virtual void Cancel() OVERRIDE;
258
259 private:
260  WebContents* web_contents_;
261  bool reader_installed_;
262  bool reader_vulnerable_;
263  WebPluginInfo reader_webplugininfo_;
264
265  DISALLOW_IMPLICIT_CONSTRUCTORS(PDFUnsupportedFeaturePromptDelegate);
266};
267
268PDFUnsupportedFeaturePromptDelegate::PDFUnsupportedFeaturePromptDelegate(
269    WebContents* web_contents,
270    const content::WebPluginInfo* reader,
271    PluginFinder* plugin_finder)
272    : web_contents_(web_contents),
273      reader_installed_(!!reader),
274      reader_vulnerable_(false) {
275  if (!reader_installed_) {
276    content::RecordAction(
277        UserMetricsAction("PDF_InstallReaderInfoBarShown"));
278    return;
279  }
280
281  content::RecordAction(UserMetricsAction("PDF_UseReaderInfoBarShown"));
282  reader_webplugininfo_ = *reader;
283
284#if defined(ENABLE_PLUGIN_INSTALLATION)
285  scoped_ptr<PluginMetadata> plugin_metadata(
286      plugin_finder->GetPluginMetadata(reader_webplugininfo_));
287
288  reader_vulnerable_ = plugin_metadata->GetSecurityStatus(*reader) !=
289                       PluginMetadata::SECURITY_STATUS_UP_TO_DATE;
290#else
291  NOTREACHED();
292#endif
293}
294
295PDFUnsupportedFeaturePromptDelegate::~PDFUnsupportedFeaturePromptDelegate() {
296}
297
298string16 PDFUnsupportedFeaturePromptDelegate::GetMessageText() const {
299  return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_MESSAGE);
300}
301
302string16 PDFUnsupportedFeaturePromptDelegate::GetAcceptButtonText() const {
303#if defined(OS_WIN)
304  if (base::win::IsMetroProcess())
305    return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_METRO_MODE_LINK);
306#endif
307
308  if (reader_installed_)
309    return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_OPEN_IN_READER_LINK);
310
311  return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_INSTALL_READER_LINK);
312}
313
314string16 PDFUnsupportedFeaturePromptDelegate::GetCancelButtonText() const {
315  return l10n_util::GetStringUTF16(IDS_DONE);
316}
317
318bool PDFUnsupportedFeaturePromptDelegate::ShouldExpire(
319    const content::LoadCommittedDetails& details) const {
320  return !details.is_in_page;
321}
322
323void PDFUnsupportedFeaturePromptDelegate::Accept() {
324#if defined(OS_WIN)
325  if (base::win::IsMetroProcess()) {
326    chrome::AttemptRestartWithModeSwitch();
327    return;
328  }
329#endif
330
331  if (!reader_installed_) {
332    content::RecordAction(UserMetricsAction("PDF_InstallReaderInfoBarOK"));
333    OpenReaderUpdateURL(web_contents_);
334    return;
335  }
336
337  content::RecordAction(UserMetricsAction("PDF_UseReaderInfoBarOK"));
338
339  if (reader_vulnerable_) {
340    new PDFUnsupportedFeatureInterstitial(web_contents_, reader_webplugininfo_);
341    return;
342  }
343
344  Profile* profile =
345      Profile::FromBrowserContext(web_contents_->GetBrowserContext());
346  OpenPDFInReaderPromptDelegate* delegate =
347      new PDFEnableAdobeReaderPromptDelegate(profile);
348
349  OpenUsingReader(web_contents_, reader_webplugininfo_, delegate);
350}
351
352void PDFUnsupportedFeaturePromptDelegate::Cancel() {
353  content::RecordAction(reader_installed_ ?
354                        UserMetricsAction("PDF_UseReaderInfoBarCancel") :
355                        UserMetricsAction("PDF_InstallReaderInfoBarCancel"));
356}
357
358#if defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION)
359void GotPluginsCallback(int process_id,
360                        int routing_id,
361                        const std::vector<content::WebPluginInfo>& plugins) {
362  WebContents* web_contents =
363      tab_util::GetWebContentsByID(process_id, routing_id);
364  if (!web_contents)
365    return;
366
367  const content::WebPluginInfo* reader = NULL;
368  PluginFinder* plugin_finder = PluginFinder::GetInstance();
369  for (size_t i = 0; i < plugins.size(); ++i) {
370    scoped_ptr<PluginMetadata> plugin_metadata(
371        plugin_finder->GetPluginMetadata(plugins[i]));
372    if (plugin_metadata->identifier() != kAdobeReaderIdentifier)
373      continue;
374
375    DCHECK(!reader);
376    reader = &plugins[i];
377    // If the Reader plugin is disabled by policy, don't prompt them.
378    Profile* profile =
379        Profile::FromBrowserContext(web_contents->GetBrowserContext());
380    PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile);
381    if (plugin_prefs->PolicyStatusForPlugin(plugin_metadata->name()) ==
382        PluginPrefs::POLICY_DISABLED) {
383      return;
384    }
385    break;
386  }
387
388  scoped_ptr<OpenPDFInReaderPromptDelegate> prompt(
389      new PDFUnsupportedFeaturePromptDelegate(
390          web_contents, reader, plugin_finder));
391  PDFTabHelper* pdf_tab_helper = PDFTabHelper::FromWebContents(web_contents);
392  pdf_tab_helper->ShowOpenInReaderPrompt(prompt.Pass());
393}
394#endif  // defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION)
395
396}  // namespace
397
398void PDFHasUnsupportedFeature(content::WebContents* web_contents) {
399#if defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION)
400  // Only works for Windows for now.  For Mac, we'll have to launch the file
401  // externally since Adobe Reader doesn't work inside Chrome.
402  PluginService::GetInstance()->GetPlugins(base::Bind(&GotPluginsCallback,
403      web_contents->GetRenderProcessHost()->GetID(),
404      web_contents->GetRenderViewHost()->GetRoutingID()));
405#endif
406}
407