// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/pdf/pdf_unsupported_feature.h" #include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/prefs/pref_service.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "base/version.h" #include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/plugins/chrome_plugin_service_filter.h" #include "chrome/browser/plugins/plugin_finder.h" #include "chrome/browser/plugins/plugin_metadata.h" #include "chrome/browser/plugins/plugin_prefs.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_preferences_util.h" #include "chrome/browser/tab_contents/tab_util.h" #include "chrome/browser/ui/pdf/open_pdf_in_reader_prompt_delegate.h" #include "chrome/browser/ui/pdf/pdf_tab_helper.h" #include "chrome/common/chrome_content_client.h" #include "chrome/common/pref_names.h" #include "content/public/browser/interstitial_page.h" #include "content/public/browser/interstitial_page_delegate.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/plugin_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/user_metrics.h" #include "content/public/browser/web_contents.h" #include "content/public/common/page_transition_types.h" #include "grit/browser_resources.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/webui/jstemplate_builder.h" #include "ui/gfx/image/image.h" #if defined(OS_WIN) #include "base/win/metro.h" #endif using base::UserMetricsAction; using content::InterstitialPage; using content::OpenURLParams; using content::PluginService; using content::Referrer; using content::WebContents; using content::WebPluginInfo; namespace { const char kAdobeReaderUpdateUrl[] = "http://www.adobe.com/go/getreader_chrome"; #if defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION) const char kAdobeReaderIdentifier[] = "adobe-reader"; #endif // The prompt delegate used to ask the user if they want to use Adobe Reader // by default. class PDFEnableAdobeReaderPromptDelegate : public OpenPDFInReaderPromptDelegate { public: explicit PDFEnableAdobeReaderPromptDelegate(Profile* profile); virtual ~PDFEnableAdobeReaderPromptDelegate(); // OpenPDFInReaderPromptDelegate virtual base::string16 GetMessageText() const OVERRIDE; virtual base::string16 GetAcceptButtonText() const OVERRIDE; virtual base::string16 GetCancelButtonText() const OVERRIDE; virtual bool ShouldExpire( const content::LoadCommittedDetails& details) const OVERRIDE; virtual void Accept() OVERRIDE; virtual void Cancel() OVERRIDE; private: void OnYes(); void OnNo(); Profile* profile_; DISALLOW_IMPLICIT_CONSTRUCTORS(PDFEnableAdobeReaderPromptDelegate); }; PDFEnableAdobeReaderPromptDelegate::PDFEnableAdobeReaderPromptDelegate( Profile* profile) : profile_(profile) { content::RecordAction(UserMetricsAction("PDF_EnableReaderInfoBarShown")); } PDFEnableAdobeReaderPromptDelegate::~PDFEnableAdobeReaderPromptDelegate() { } bool PDFEnableAdobeReaderPromptDelegate::ShouldExpire( const content::LoadCommittedDetails& details) const { content::PageTransition transition = content::PageTransitionStripQualifier(details.entry->GetTransitionType()); // We don't want to expire on a reload, because that is how we open the PDF in // Reader. return !details.is_in_page && transition != content::PAGE_TRANSITION_RELOAD; } void PDFEnableAdobeReaderPromptDelegate::Accept() { content::RecordAction(UserMetricsAction("PDF_EnableReaderInfoBarOK")); PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get(); plugin_prefs->EnablePluginGroup( true, base::ASCIIToUTF16(PluginMetadata::kAdobeReaderGroupName)); plugin_prefs->EnablePluginGroup( false, base::ASCIIToUTF16(ChromeContentClient::kPDFPluginName)); } void PDFEnableAdobeReaderPromptDelegate::Cancel() { content::RecordAction(UserMetricsAction("PDF_EnableReaderInfoBarCancel")); } base::string16 PDFEnableAdobeReaderPromptDelegate::GetAcceptButtonText() const { return l10n_util::GetStringUTF16(IDS_PDF_INFOBAR_ALWAYS_USE_READER_BUTTON); } base::string16 PDFEnableAdobeReaderPromptDelegate::GetCancelButtonText() const { return l10n_util::GetStringUTF16(IDS_DONE); } base::string16 PDFEnableAdobeReaderPromptDelegate::GetMessageText() const { return l10n_util::GetStringUTF16(IDS_PDF_INFOBAR_QUESTION_ALWAYS_USE_READER); } // Launch the url to get the latest Adbobe Reader installer. void OpenReaderUpdateURL(WebContents* web_contents) { OpenURLParams params( GURL(kAdobeReaderUpdateUrl), Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, false); web_contents->OpenURL(params); } // Opens the PDF using Adobe Reader. void OpenUsingReader(WebContents* web_contents, const WebPluginInfo& reader_plugin, OpenPDFInReaderPromptDelegate* delegate) { ChromePluginServiceFilter::GetInstance()->OverridePluginForFrame( web_contents->GetRenderProcessHost()->GetID(), web_contents->GetMainFrame()->GetRoutingID(), web_contents->GetURL(), reader_plugin); web_contents->GetRenderViewHost()->ReloadFrame(); PDFTabHelper* pdf_tab_helper = PDFTabHelper::FromWebContents(web_contents); if (delegate) pdf_tab_helper->ShowOpenInReaderPrompt(make_scoped_ptr(delegate)); } // An interstitial to be used when the user chooses to open a PDF using Adobe // Reader, but it is out of date. class PDFUnsupportedFeatureInterstitial : public content::InterstitialPageDelegate { public: PDFUnsupportedFeatureInterstitial( WebContents* web_contents, const WebPluginInfo& reader_webplugininfo) : web_contents_(web_contents), reader_webplugininfo_(reader_webplugininfo) { content::RecordAction(UserMetricsAction("PDF_ReaderInterstitialShown")); interstitial_page_ = InterstitialPage::Create( web_contents, false, web_contents->GetURL(), this); interstitial_page_->Show(); } protected: // InterstitialPageDelegate implementation. virtual std::string GetHTMLContents() OVERRIDE { base::DictionaryValue strings; strings.SetString( "title", l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_TITLE)); strings.SetString( "headLine", l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_BODY)); strings.SetString( "update", l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_UPDATE)); strings.SetString( "open_with_reader", l10n_util::GetStringUTF16( IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_PROCEED)); strings.SetString( "ok", l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_OK)); strings.SetString( "cancel", l10n_util::GetStringUTF16(IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_CANCEL)); base::StringPiece html(ResourceBundle::GetSharedInstance(). GetRawDataResource(IDR_READER_OUT_OF_DATE_HTML)); return webui::GetI18nTemplateHtml(html, &strings); } virtual void CommandReceived(const std::string& command) OVERRIDE { if (command == "0") { content::RecordAction( UserMetricsAction("PDF_ReaderInterstitialCancel")); interstitial_page_->DontProceed(); return; } if (command == "1") { content::RecordAction( UserMetricsAction("PDF_ReaderInterstitialUpdate")); OpenReaderUpdateURL(web_contents_); } else if (command == "2") { content::RecordAction( UserMetricsAction("PDF_ReaderInterstitialIgnore")); // Pretend that the plug-in is up-to-date so that we don't block it. reader_webplugininfo_.version = base::ASCIIToUTF16("11.0.0.0"); OpenUsingReader(web_contents_, reader_webplugininfo_, NULL); } else { NOTREACHED(); } interstitial_page_->Proceed(); } virtual void OverrideRendererPrefs( content::RendererPreferences* prefs) OVERRIDE { Profile* profile = Profile::FromBrowserContext(web_contents_->GetBrowserContext()); renderer_preferences_util::UpdateFromSystemSettings(prefs, profile); } private: WebContents* web_contents_; WebPluginInfo reader_webplugininfo_; InterstitialPage* interstitial_page_; // Owns us. DISALLOW_COPY_AND_ASSIGN(PDFUnsupportedFeatureInterstitial); }; // The delegate for the bubble used to inform the user that we don't support a // feature in the PDF. class PDFUnsupportedFeaturePromptDelegate : public OpenPDFInReaderPromptDelegate { public: // |reader| is NULL if Adobe Reader isn't installed. PDFUnsupportedFeaturePromptDelegate(WebContents* web_contents, const content::WebPluginInfo* reader, PluginFinder* plugin_finder); virtual ~PDFUnsupportedFeaturePromptDelegate(); // OpenPDFInReaderPromptDelegate: virtual base::string16 GetMessageText() const OVERRIDE; virtual base::string16 GetAcceptButtonText() const OVERRIDE; virtual base::string16 GetCancelButtonText() const OVERRIDE; virtual bool ShouldExpire( const content::LoadCommittedDetails& details) const OVERRIDE; virtual void Accept() OVERRIDE; virtual void Cancel() OVERRIDE; private: WebContents* web_contents_; bool reader_installed_; bool reader_vulnerable_; WebPluginInfo reader_webplugininfo_; DISALLOW_IMPLICIT_CONSTRUCTORS(PDFUnsupportedFeaturePromptDelegate); }; PDFUnsupportedFeaturePromptDelegate::PDFUnsupportedFeaturePromptDelegate( WebContents* web_contents, const content::WebPluginInfo* reader, PluginFinder* plugin_finder) : web_contents_(web_contents), reader_installed_(!!reader), reader_vulnerable_(false) { if (!reader_installed_) { content::RecordAction( UserMetricsAction("PDF_InstallReaderInfoBarShown")); return; } content::RecordAction(UserMetricsAction("PDF_UseReaderInfoBarShown")); reader_webplugininfo_ = *reader; #if defined(ENABLE_PLUGIN_INSTALLATION) scoped_ptr plugin_metadata( plugin_finder->GetPluginMetadata(reader_webplugininfo_)); reader_vulnerable_ = plugin_metadata->GetSecurityStatus(*reader) != PluginMetadata::SECURITY_STATUS_UP_TO_DATE; #else NOTREACHED(); #endif } PDFUnsupportedFeaturePromptDelegate::~PDFUnsupportedFeaturePromptDelegate() { } base::string16 PDFUnsupportedFeaturePromptDelegate::GetMessageText() const { return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_MESSAGE); } base::string16 PDFUnsupportedFeaturePromptDelegate::GetAcceptButtonText() const { #if defined(OS_WIN) if (base::win::IsMetroProcess()) return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_METRO_MODE_LINK); #endif if (reader_installed_) return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_OPEN_IN_READER_LINK); return l10n_util::GetStringUTF16(IDS_PDF_BUBBLE_INSTALL_READER_LINK); } base::string16 PDFUnsupportedFeaturePromptDelegate::GetCancelButtonText() const { return l10n_util::GetStringUTF16(IDS_DONE); } bool PDFUnsupportedFeaturePromptDelegate::ShouldExpire( const content::LoadCommittedDetails& details) const { return !details.is_in_page; } void PDFUnsupportedFeaturePromptDelegate::Accept() { #if defined(OS_WIN) if (base::win::IsMetroProcess()) { chrome::AttemptRestartWithModeSwitch(); return; } #endif if (!reader_installed_) { content::RecordAction(UserMetricsAction("PDF_InstallReaderInfoBarOK")); OpenReaderUpdateURL(web_contents_); return; } content::RecordAction(UserMetricsAction("PDF_UseReaderInfoBarOK")); if (reader_vulnerable_) { new PDFUnsupportedFeatureInterstitial(web_contents_, reader_webplugininfo_); return; } Profile* profile = Profile::FromBrowserContext(web_contents_->GetBrowserContext()); OpenPDFInReaderPromptDelegate* delegate = new PDFEnableAdobeReaderPromptDelegate(profile); OpenUsingReader(web_contents_, reader_webplugininfo_, delegate); } void PDFUnsupportedFeaturePromptDelegate::Cancel() { content::RecordAction(reader_installed_ ? UserMetricsAction("PDF_UseReaderInfoBarCancel") : UserMetricsAction("PDF_InstallReaderInfoBarCancel")); } #if defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION) void GotPluginsCallback(int process_id, int routing_id, const std::vector& plugins) { WebContents* web_contents = tab_util::GetWebContentsByID(process_id, routing_id); if (!web_contents) return; const content::WebPluginInfo* reader = NULL; PluginFinder* plugin_finder = PluginFinder::GetInstance(); for (size_t i = 0; i < plugins.size(); ++i) { scoped_ptr plugin_metadata( plugin_finder->GetPluginMetadata(plugins[i])); if (plugin_metadata->identifier() != kAdobeReaderIdentifier) continue; DCHECK(!reader); reader = &plugins[i]; // If the Reader plugin is disabled by policy, don't prompt them. Profile* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile); if (plugin_prefs->PolicyStatusForPlugin(plugin_metadata->name()) == PluginPrefs::POLICY_DISABLED) { return; } break; } scoped_ptr prompt( new PDFUnsupportedFeaturePromptDelegate( web_contents, reader, plugin_finder)); PDFTabHelper* pdf_tab_helper = PDFTabHelper::FromWebContents(web_contents); pdf_tab_helper->ShowOpenInReaderPrompt(prompt.Pass()); } #endif // defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION) } // namespace void PDFHasUnsupportedFeature(content::WebContents* web_contents) { #if defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION) // Only works for Windows for now. For Mac, we'll have to launch the file // externally since Adobe Reader doesn't work inside Chrome. PluginService::GetInstance()->GetPlugins(base::Bind(&GotPluginsCallback, web_contents->GetRenderProcessHost()->GetID(), web_contents->GetRenderViewHost()->GetRoutingID())); #endif }