diff options
33 files changed, 557 insertions, 51 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index ee7596b..9fc08e7 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -11144,6 +11144,35 @@ Keep your key file in a safe place. You will need it to create new versions of y The credentials used to share your printers to <ph name="CLOUD_PRINT_NAME">Google Cloud Print</ph> have expired. Please click here to re-enter your username and password. </message> + <!-- PDF with unsupported feature Info Bar --> + <message name="IDS_PDF_INFOBAR_QUESTION_READER_INSTALLED" desc="Question asked on the info bar when a user views a PDF with an unsupported feature and they have Adobe Reader installed."> + Parts of this PDF document could not be displayed. View with Adobe Reader? + </message> + + <message name="IDS_PDF_INFOBAR_QUESTION_READER_NOT_INSTALLED" desc="Question asked on the info bar when a user views a PDF with an unsupported feature and they don't have Adobe Reader installed."> + Parts of this PDF document could not be displayed. Install Adobe Reader? + </message> + + <!-- Adobe Reader is out of date Blocking Page --> + <message name="IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_TITLE" desc="The title of the Adobe Reader out of date blocking page."> + Adobe Reader Out Of Date + </message> + <message name="IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_BODY" desc="The body of the Adobe Reader out of date blocking page."> + Adobe Reader is out of date and may be insecure. + </message> + <message name="IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_UPDATE" desc="The name of the radio button option to go to the Adobe Reader installer website."> + Update Adobe Reader now + </message> + <message name="IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_PROCEED" desc="The name of the radio button to proceed to open the PDF with the out of date Adobe Reader."> + Proceed without updating Adobe Reader (not recommended) + </message> + <message name="IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_OK" desc="OK button text of the Adobe Reader out of date blocking page."> + OK + </message> + <message name="IDS_READER_OUT_OF_DATE_BLOCKING_PAGE_CANCEL" desc="Cancel button text of the Adobe Reader out of date blocking page."> + Cancel + </message> + </messages> <includes> <if expr="pp_ifdef('_google_chrome')"> diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index 27beb5f..907deb4 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -52,6 +52,7 @@ without changes to the corresponding grd file. etaa --> <include name="IDR_NOTIFICATION_ICON_HTML" file="resources\notification_icon.html" type="BINDATA" /> <include name="IDR_OPTIONS_HTML" file="resources\options\options.html" flattenhtml="true" type="BINDATA" /> <include name="IDR_PLUGINS_HTML" file="resources\plugins.html" flattenhtml="true" type="BINDATA" /> + <include name="IDR_READER_OUT_OF_DATE_HTML" file="resources\reader_out_of_date.html" flattenhtml="true" type="BINDATA" /> <include name="IDR_PRINT_PREVIEW_HTML" file="resources\print_preview.html" flattenhtml="true" type="BINDATA" /> <include name="IDR_SAFE_BROWSING_MALWARE_BLOCK" file="resources\safe_browsing_malware_block.html" flattenhtml="true" type="BINDATA" /> <include name="IDR_SAFE_BROWSING_MULTIPLE_THREAT_BLOCK" file="resources\safe_browsing_multiple_threat_block.html" flattenhtml="true" type="BINDATA" /> diff --git a/chrome/browser/pdf_unsupported_feature.cc b/chrome/browser/pdf_unsupported_feature.cc new file mode 100644 index 0000000..2e89da2 --- /dev/null +++ b/chrome/browser/pdf_unsupported_feature.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2011 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/pdf_unsupported_feature.h" + +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/version.h" +#include "chrome/browser/plugin_service.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/tab_contents/infobar_delegate.h" +#include "chrome/browser/tab_contents/interstitial_page.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/jstemplate_builder.h" +#include "grit/browser_resources.h" +#include "grit/generated_resources.h" +#include "webkit/plugins/npapi/plugin_group.h" +#include "webkit/plugins/npapi/plugin_list.h" +#include "webkit/plugins/npapi/webplugininfo.h" + +using webkit::npapi::PluginGroup; +using webkit::npapi::PluginList; +using webkit::npapi::WebPluginInfo; + +// Only launch Adobe Reader X or later. +static const uint16 kMinReaderVersionToUse = 10; + +namespace { + +// Launch the url to get the latest Adbobe Reader installer. +void OpenReaderUpdateURL(TabContents* tab) { + tab->OpenURL(GURL(PluginGroup::kAdobeReaderUpdateURL), GURL(), CURRENT_TAB, + PageTransition::LINK); +} + +// Opens the PDF using Adobe Reader. +void OpenUsingReader(TabContents* tab, const WebPluginInfo& reader_plugin) { + PluginService::OverriddenPlugin plugin; + plugin.render_process_id = tab->GetRenderProcessHost()->id(); + plugin.render_view_id = tab->render_view_host()->routing_id(); + plugin.url = tab->GetURL(); + plugin.plugin = reader_plugin; + + PluginService::GetInstance()->OverridePluginForTab(plugin); + tab->render_view_host()->ReloadFrame(); +} + +// 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 InterstitialPage { + public: + PDFUnsupportedFeatureInterstitial( + TabContents* tab, + const WebPluginInfo& reader_webplugininfo) + : InterstitialPage(tab, false, tab->GetURL()), + reader_webplugininfo_(reader_webplugininfo) { + } + + protected: + // InterstitialPage implementation. + virtual std::string GetHTMLContents() { + 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 jstemplate_builder::GetI18nTemplateHtml(html, &strings); + } + + virtual void CommandReceived(const std::string& command) { + if (command == "0") { + DontProceed(); + return; + } + + if (command == "1") { + OpenReaderUpdateURL(tab()); + } else if (command == "2") { + OpenUsingReader(tab(), reader_webplugininfo_); + } else { + NOTREACHED(); + } + Proceed(); + } + + private: + WebPluginInfo reader_webplugininfo_; + + DISALLOW_COPY_AND_ASSIGN(PDFUnsupportedFeatureInterstitial); +}; + +// The info bar delegate used to inform the user that we don't support a feature +// in the PDF. +class PDFUnsupportedFeatureConfirmInfoBarDelegate + : public ConfirmInfoBarDelegate { + public: + PDFUnsupportedFeatureConfirmInfoBarDelegate( + TabContents* tab_contents, + PluginGroup* reader_group) // NULL if Adobe Reader isn't installed. + : ConfirmInfoBarDelegate(tab_contents), + tab_contents_(tab_contents), + reader_installed_(!!reader_group), + reader_vulnerable_(false) { + if (reader_installed_) { + std::vector<WebPluginInfo> plugins = reader_group->web_plugin_infos(); + DCHECK_EQ(plugins.size(), 1u); + reader_webplugininfo_ = plugins[0]; + + reader_vulnerable_ = reader_group->IsVulnerable(); + if (!reader_vulnerable_) { + scoped_ptr<Version> version(PluginGroup::CreateVersionFromString( + reader_webplugininfo_.version)); + if (version.get()) { + if (version->components()[0] < kMinReaderVersionToUse) + reader_vulnerable_ = true; + } + } + } + } + + // ConfirmInfoBarDelegate + virtual void InfoBarClosed() { + delete this; + } + virtual Type GetInfoBarType() { + return PAGE_ACTION_TYPE; + } + virtual bool Accept() { + LaunchReader(); + return true; + } + virtual int GetButtons() const { + return BUTTON_OK | BUTTON_CANCEL; + } + virtual string16 GetButtonLabel(InfoBarButton button) const { + switch (button) { + case BUTTON_OK: + return l10n_util::GetStringUTF16( + IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL); + case BUTTON_CANCEL: + return l10n_util::GetStringUTF16( + IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL); + default: + // All buttons are labeled above. + NOTREACHED() << "Bad button id " << button; + return string16(); + } + } + virtual string16 GetMessageText() const { + return l10n_util::GetStringUTF16(reader_installed_ ? + IDS_PDF_INFOBAR_QUESTION_READER_INSTALLED : + IDS_PDF_INFOBAR_QUESTION_READER_NOT_INSTALLED); + } + + private: + void LaunchReader() { + if (!reader_installed_) { + OpenReaderUpdateURL(tab_contents_); + return; + } + + if (reader_vulnerable_) { + PDFUnsupportedFeatureInterstitial* interstitial = new + PDFUnsupportedFeatureInterstitial( + tab_contents_, reader_webplugininfo_); + interstitial->Show(); + return; + } + + OpenUsingReader(tab_contents_, reader_webplugininfo_); + } + + TabContents* tab_contents_; + bool reader_installed_; + bool reader_vulnerable_; + WebPluginInfo reader_webplugininfo_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(PDFUnsupportedFeatureConfirmInfoBarDelegate); +}; + +} // namespace + +void PDFHasUnsupportedFeature(TabContents* tab) { +#if !defined(OS_WIN) + // Only works for Windows for now. For Mac, we'll have to launch the file + // externally since Adobe Reader doesn't work inside Chrome. + return; +#endif + + PluginGroup* reader_group = NULL; + std::vector<PluginGroup> plugin_groups; + PluginList::Singleton()->GetPluginGroups( + false, &plugin_groups); + string16 reader_group_name(UTF8ToUTF16(PluginGroup::kAdobeReaderGroupName)); + for (size_t i = 0; i < plugin_groups.size(); ++i) { + if (plugin_groups[i].GetGroupName() == reader_group_name) { + reader_group = &plugin_groups[i]; + break; + } + } + + // If the plugin is disabled by policy or by the user, don't prompt them. + if (reader_group && !reader_group->Enabled()) + return; + + tab->AddInfoBar(new PDFUnsupportedFeatureConfirmInfoBarDelegate( + tab, reader_group)); +} diff --git a/chrome/browser/pdf_unsupported_feature.h b/chrome/browser/pdf_unsupported_feature.h new file mode 100644 index 0000000..e6e4f64 --- /dev/null +++ b/chrome/browser/pdf_unsupported_feature.h @@ -0,0 +1,19 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_BROWSER_PDF_UNSUPPORTED_FEATURE_H_ +#define CHROME_BROWSER_PDF_UNSUPPORTED_FEATURE_H_ +#pragma once + +#include "base/basictypes.h" + +class TabContents; + +// Call this when a tab encounters a PDF that has features which our internal +// viewer doesn't support. Will take care of puting up an infobar to inform the +// user and launch Reader if they choose. If Reader is out of date, it will put +// up an interstitial. +void PDFHasUnsupportedFeature(TabContents* tab); + +#endif // CHROME_BROWSER_PDF_UNSUPPORTED_FEATURE_H_ diff --git a/chrome/browser/plugin_data_remover.cc b/chrome/browser/plugin_data_remover.cc index 89e0ece..bfc9811 100644 --- a/chrome/browser/plugin_data_remover.cc +++ b/chrome/browser/plugin_data_remover.cc @@ -50,7 +50,7 @@ base::WaitableEvent* PluginDataRemover::StartRemoving( AddRef(); PluginService::GetInstance()->OpenChannelToPlugin( - GURL(), mime_type_, this); + 0, 0, GURL(), mime_type_, this); BrowserThread::PostDelayedTask( BrowserThread::IO, diff --git a/chrome/browser/plugin_service.cc b/chrome/browser/plugin_service.cc index b9774e8..4cf18ec 100644 --- a/chrome/browser/plugin_service.cc +++ b/chrome/browser/plugin_service.cc @@ -20,6 +20,7 @@ #include "chrome/browser/plugin_updater.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/common/chrome_plugin_lib.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" @@ -211,6 +212,9 @@ PluginService::PluginService() #endif registrar_.Add(this, NotificationType::PLUGIN_ENABLE_STATUS_CHANGED, NotificationService::AllSources()); + registrar_.Add(this, + NotificationType::RENDERER_PROCESS_CLOSED, + NotificationService::AllSources()); } PluginService::~PluginService() { @@ -285,6 +289,8 @@ PluginProcessHost* PluginService::FindOrStartPluginProcess( } void PluginService::OpenChannelToPlugin( + int render_process_id, + int render_view_id, const GURL& url, const std::string& mime_type, PluginProcessHost::Client* client) { @@ -294,16 +300,19 @@ void PluginService::OpenChannelToPlugin( BrowserThread::FILE, FROM_HERE, NewRunnableMethod( this, &PluginService::GetAllowedPluginForOpenChannelToPlugin, - url, mime_type, client)); + render_process_id, render_view_id, url, mime_type, client)); } void PluginService::GetAllowedPluginForOpenChannelToPlugin( + int render_process_id, + int render_view_id, const GURL& url, const std::string& mime_type, PluginProcessHost::Client* client) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); webkit::npapi::WebPluginInfo info; - bool found = GetFirstAllowedPluginInfo(url, mime_type, &info, NULL); + bool found = GetFirstAllowedPluginInfo( + render_process_id, render_view_id, url, mime_type, &info, NULL); FilePath plugin_path; if (found && info.enabled) plugin_path = FilePath(info.path); @@ -329,6 +338,8 @@ void PluginService::FinishOpenChannelToPlugin( } bool PluginService::GetFirstAllowedPluginInfo( + int render_process_id, + int render_view_id, const GURL& url, const std::string& mime_type, webkit::npapi::WebPluginInfo* info, @@ -354,6 +365,18 @@ bool PluginService::GetFirstAllowedPluginInfo( } return false; #else + { + AutoLock auto_lock(overridden_plugins_lock_); + for (size_t i = 0; i < overridden_plugins_.size(); ++i) { + if (overridden_plugins_[i].render_process_id == render_process_id && + overridden_plugins_[i].render_view_id == render_view_id && + overridden_plugins_[i].url == url) { + *actual_mime_type = mime_type; + *info = overridden_plugins_[i].plugin; + return true; + } + } + } return webkit::npapi::PluginList::Singleton()->GetPluginInfo( url, mime_type, allow_wildcard, info, actual_mime_type); #endif @@ -436,6 +459,18 @@ void PluginService::Observe(NotificationType type, PurgePluginListCache(false); break; } + case NotificationType::RENDERER_PROCESS_CLOSED: { + int render_process_id = Source<RenderProcessHost>(source).ptr()->id(); + + AutoLock auto_lock(overridden_plugins_lock_); + for (size_t i = 0; i < overridden_plugins_.size(); ++i) { + if (overridden_plugins_[i].render_process_id == render_process_id) { + overridden_plugins_.erase(overridden_plugins_.begin() + i); + break; + } + } + break; + } default: NOTREACHED(); } @@ -457,6 +492,11 @@ bool PluginService::PrivatePluginAllowedForURL(const FilePath& plugin_path, url.host() == required_url.host()); } +void PluginService::OverridePluginForTab(OverriddenPlugin plugin) { + AutoLock auto_lock(overridden_plugins_lock_); + overridden_plugins_.push_back(plugin); +} + void PluginService::RegisterPepperPlugins() { std::vector<PepperPluginInfo> plugins; PepperPluginRegistry::GetList(&plugins); diff --git a/chrome/browser/plugin_service.h b/chrome/browser/plugin_service.h index 5e03c09..f2732b2 100644 --- a/chrome/browser/plugin_service.h +++ b/chrome/browser/plugin_service.h @@ -16,6 +16,7 @@ #include "base/hash_tables.h" #include "base/scoped_vector.h" #include "base/singleton.h" +#include "base/synchronization/lock.h" #include "base/synchronization/waitable_event_watcher.h" #include "build/build_config.h" #include "chrome/browser/plugin_process_host.h" @@ -23,6 +24,7 @@ #include "chrome/common/notification_registrar.h" #include "googleurl/src/gurl.h" #include "ipc/ipc_channel_handle.h" +#include "webkit/plugins/npapi/webplugininfo.h" #if defined(OS_WIN) #include "base/scoped_ptr.h" @@ -52,18 +54,19 @@ namespace net { class URLRequestContext; } // namespace net -namespace webkit { -namespace npapi { -struct WebPluginInfo; -} -} - // This must be created on the main thread but it's only called on the IO/file // thread. class PluginService : public base::WaitableEventWatcher::Delegate, public NotificationObserver { public: + struct OverriddenPlugin { + int render_process_id; + int render_view_id; + GURL url; + webkit::npapi::WebPluginInfo plugin; + }; + // Initializes the global instance; should be called on startup from the main // thread. static void InitGlobalInstance(Profile* profile); @@ -97,13 +100,17 @@ class PluginService // Opens a channel to a plugin process for the given mime type, starting // a new plugin process if necessary. This must be called on the IO thread // or else a deadlock can occur. - void OpenChannelToPlugin(const GURL& url, + void OpenChannelToPlugin(int render_process_id, + int render_view_id, + const GURL& url, const std::string& mime_type, PluginProcessHost::Client* client); // Gets the first allowed plugin in the list of plugins that matches // the given url and mime type. Must be called on the FILE thread. - bool GetFirstAllowedPluginInfo(const GURL& url, + bool GetFirstAllowedPluginInfo(int render_process_id, + int render_view_id, + const GURL& url, const std::string& mime_type, webkit::npapi::WebPluginInfo* info, std::string* actual_mime_type); @@ -112,6 +119,9 @@ class PluginService // the given URL. bool PrivatePluginAllowedForURL(const FilePath& plugin_path, const GURL& url); + // Safe to be called from any thread. + void OverridePluginForTab(OverriddenPlugin plugin); + // The UI thread's message loop MessageLoop* main_message_loop() { return main_message_loop_; } @@ -140,6 +150,8 @@ class PluginService // Helper so we can do the plugin lookup on the FILE thread. void GetAllowedPluginForOpenChannelToPlugin( + int render_process_id, + int render_view_id, const GURL& url, const std::string& mime_type, PluginProcessHost::Client* client); @@ -203,6 +215,9 @@ class PluginService // Set to true if chrome plugins are enabled. Defaults to true. static bool enable_chrome_plugins_; + std::vector<OverriddenPlugin> overridden_plugins_; + base::Lock overridden_plugins_lock_; + DISALLOW_COPY_AND_ASSIGN(PluginService); }; diff --git a/chrome/browser/plugin_service_browsertest.cc b/chrome/browser/plugin_service_browsertest.cc index 5fed897..3f31159 100644 --- a/chrome/browser/plugin_service_browsertest.cc +++ b/chrome/browser/plugin_service_browsertest.cc @@ -82,7 +82,7 @@ IN_PROC_BROWSER_TEST_F(PluginServiceTest, StartAndFindPluginProcess) { IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToPlugin) { MockPluginProcessHostClient mock_client; EXPECT_CALL(mock_client, SetPluginInfo(testing::_)).Times(1); - plugin_service_->OpenChannelToPlugin(GURL("http://google.com/"), + plugin_service_->OpenChannelToPlugin(0, 0, GURL("http://google.com/"), "audio/mp3", &mock_client); message_loop_.RunAllPending(); @@ -97,7 +97,7 @@ IN_PROC_BROWSER_TEST_F(PluginServiceTest, GetFirstAllowedPluginInfo) { // supports all mime types. webkit::npapi::WebPluginInfo plugin_info; std::string plugin_mime_type; - plugin_service_->GetFirstAllowedPluginInfo(GURL("http://google.com/"), + plugin_service_->GetFirstAllowedPluginInfo(0, 0, GURL("http://google.com/"), "application/pdf", &plugin_info, &plugin_mime_type); diff --git a/chrome/browser/renderer_host/render_message_filter.cc b/chrome/browser/renderer_host/render_message_filter.cc index 4156424..e00a6b8 100644 --- a/chrome/browser/renderer_host/render_message_filter.cc +++ b/chrome/browser/renderer_host/render_message_filter.cc @@ -648,7 +648,8 @@ void RenderMessageFilter::OnGetPluginsOnFileThread( NewRunnableMethod(this, &RenderMessageFilter::Send, reply_msg)); } -void RenderMessageFilter::OnGetPluginInfo(const GURL& url, +void RenderMessageFilter::OnGetPluginInfo(int routing_id, + const GURL& url, const GURL& policy_url, const std::string& mime_type, IPC::Message* reply_msg) { @@ -658,20 +659,20 @@ void RenderMessageFilter::OnGetPluginInfo(const GURL& url, BrowserThread::FILE, FROM_HERE, NewRunnableMethod( this, &RenderMessageFilter::OnGetPluginInfoOnFileThread, - url, policy_url, mime_type, reply_msg)); + routing_id, url, policy_url, mime_type, reply_msg)); } void RenderMessageFilter::OnGetPluginInfoOnFileThread( + int render_view_id, const GURL& url, const GURL& policy_url, const std::string& mime_type, IPC::Message* reply_msg) { std::string actual_mime_type; webkit::npapi::WebPluginInfo info; - bool found = plugin_service_->GetFirstAllowedPluginInfo(url, - mime_type, - &info, - &actual_mime_type); + bool found = plugin_service_->GetFirstAllowedPluginInfo( + render_process_id_, render_view_id, url, mime_type, &info, + &actual_mime_type); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, NewRunnableMethod( @@ -704,12 +705,12 @@ void RenderMessageFilter::OnGotPluginInfo( Send(reply_msg); } -void RenderMessageFilter::OnOpenChannelToPlugin(const GURL& url, +void RenderMessageFilter::OnOpenChannelToPlugin(int routing_id, + const GURL& url, const std::string& mime_type, IPC::Message* reply_msg) { plugin_service_->OpenChannelToPlugin( - url, - mime_type, + render_process_id_, routing_id, url, mime_type, new OpenChannelToPluginCallback(this, reply_msg)); } diff --git a/chrome/browser/renderer_host/render_message_filter.h b/chrome/browser/renderer_host/render_message_filter.h index c4af367..dee75a0 100644 --- a/chrome/browser/renderer_host/render_message_filter.h +++ b/chrome/browser/renderer_host/render_message_filter.h @@ -143,11 +143,13 @@ class RenderMessageFilter : public BrowserMessageFilter, #endif void OnGetPlugins(bool refresh, IPC::Message* reply_msg); void OnGetPluginsOnFileThread(bool refresh, IPC::Message* reply_msg); - void OnGetPluginInfo(const GURL& url, + void OnGetPluginInfo(int routing_id, + const GURL& url, const GURL& policy_url, const std::string& mime_type, IPC::Message* reply_msg); - void OnGetPluginInfoOnFileThread(const GURL& url, + void OnGetPluginInfoOnFileThread(int render_view_id, + const GURL& url, const GURL& policy_url, const std::string& mime_type, IPC::Message* reply_msg); @@ -156,7 +158,8 @@ class RenderMessageFilter : public BrowserMessageFilter, const std::string& actual_mime_type, const GURL& policy_url, IPC::Message* reply_msg); - void OnOpenChannelToPlugin(const GURL& url, + void OnOpenChannelToPlugin(int routing_id, + const GURL& url, const std::string& mime_type, IPC::Message* reply_msg); void OnOpenChannelToPepperPlugin(const FilePath& path, diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc index 5c5e9b3..fc62759 100644 --- a/chrome/browser/renderer_host/render_view_host.cc +++ b/chrome/browser/renderer_host/render_view_host.cc @@ -847,8 +847,6 @@ bool RenderViewHost::OnMessageReceived(const IPC::Message& msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_DetectedPhishingSite, OnDetectedPhishingSite) IPC_MESSAGE_HANDLER(ViewHostMsg_ScriptEvalResponse, OnScriptEvalResponse) - IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateContentRestrictions, - OnUpdateContentRestrictions) #if defined(OS_MACOSX) IPC_MESSAGE_HANDLER(ViewHostMsg_ShowPopup, OnMsgShowPopup) #endif @@ -1925,10 +1923,6 @@ void RenderViewHost::OnScriptEvalResponse(int id, const ListValue& result) { Details<std::pair<int, Value*> >(&details)); } -void RenderViewHost::OnUpdateContentRestrictions(int restrictions) { - delegate_->UpdateContentRestrictions(restrictions); -} - #if defined(OS_MACOSX) void RenderViewHost::OnMsgShowPopup( const ViewHostMsg_ShowPopup_Params& params) { diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h index 4b8619e..1174588 100644 --- a/chrome/browser/renderer_host/render_view_host.h +++ b/chrome/browser/renderer_host/render_view_host.h @@ -684,7 +684,6 @@ class RenderViewHost : public RenderWidgetHost { void OnInstantSupportDetermined(int32 page_id, bool result); void OnDetectedPhishingSite(const GURL& phishing_url, double phishing_score); void OnScriptEvalResponse(int id, const ListValue& result); - void OnUpdateContentRestrictions(int restrictions); void OnPagesReadyForPreview( const ViewHostMsg_DidPreviewDocument_Params& params); diff --git a/chrome/browser/renderer_host/render_view_host_delegate.h b/chrome/browser/renderer_host/render_view_host_delegate.h index 6bd228e..ccd7e01 100644 --- a/chrome/browser/renderer_host/render_view_host_delegate.h +++ b/chrome/browser/renderer_host/render_view_host_delegate.h @@ -745,9 +745,6 @@ class RenderViewHostDelegate : public IPC::Channel::Listener { int maximum_percent, bool remember) {} - // Update the content restrictions, i.e. disable print/copy. - virtual void UpdateContentRestrictions(int restrictions) {} - protected: virtual ~RenderViewHostDelegate() {} }; diff --git a/chrome/browser/resources/reader_out_of_date.html b/chrome/browser/resources/reader_out_of_date.html new file mode 100644 index 0000000..df20c5b --- /dev/null +++ b/chrome/browser/resources/reader_out_of_date.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<html> +<head> +<title i18n-content="title"></title> +<style type="text/css"> +body { + background-color:#500; + font-family:Helvetica,Arial,sans-serif; + margin:0px; +} +.background { + position:absolute; + width:100%; + height:100%; +} +.cell { + padding:40px; +} +.box { + width:80%; + background-color:white; + color:black; + font-size:10pt; + line-height:16pt; + text-align:left; + padding:20px; + position:relative; + -webkit-box-shadow:3px 3px 8px #200; + border-radius:5px; +} +html[dir='rtl'] .box { + text-align:right; +} + +.icon { + position:absolute; +} +.title { + margin: 0px 77px 0px; + font-size:18pt; + line-height: 140%; + margin-bottom:6pt; + font-weight:bold; + color:#660000; +} +.main { + margin:0px 80px 0px; +} + +.submission { + margin:15px 5px 15px 0px; + padding:0px; +} +input { + margin:0px; +} +.proceedbutton { +} +</style> + +<script> +function sendCommand(cmd) { + window.domAutomationController.setAutomationId(1); + window.domAutomationController.send(cmd); +} + +function getChecked() { + for (var i = 0; i < document.form.group.length; i++) { + if (document.form.group[i].checked) + return parseInt(document.form.group[i].value); + } + return 0; +} +</script> +</head> +<body oncontextmenu="return false;"> +<div class="background"><img src="ssl_roadblock_background.png" width="100%" height="100%" alt="background" onmousedown="return false;"></div> +<table width="100%" cellspacing="0" cellpadding="0"> + <td class="cell" valign="middle" align="center"> + <div class="box"> + <div class="icon"><img src="ssl_roadblock_icon.png" alt="Reader out of date icon" onmousedown="return false;"></div> + <div class="title" i18n-content="headLine"></div> + <div class="main"> + <form name="form" class="submission"> + <input type="radio" name="group" value="1" checked> <span i18n-content="update"></span><br> + <input type="radio" name="group" value="2"> <span i18n-content="open_with_reader"></span><br> + <br> + <input type="button" i18n-values="value:ok" name="ok" class="proceedbutton" onClick="sendCommand(getChecked());"> + <input type="button" i18n-values="value:cancel" name="cancel" onClick="sendCommand(0);"> + </form> + </div> + </td> +</table> +</body> +</html> diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index 758a1d0..938c9bf 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -47,6 +47,7 @@ #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/modal_html_dialog_delegate.h" #include "chrome/browser/omnibox_search_hint.h" +#include "chrome/browser/pdf_unsupported_feature.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/plugin_installer_infobar_delegate.h" #include "chrome/browser/prefs/pref_service.h" @@ -615,6 +616,10 @@ bool TabContents::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentLoadedInFrame, OnDocumentLoadedInFrame) IPC_MESSAGE_HANDLER(ViewHostMsg_DidFinishLoad, OnDidFinishLoad) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateContentRestrictions, + OnUpdateContentRestrictions) + IPC_MESSAGE_HANDLER(ViewHostMsg_PDFHasUnsupportedFeature, + OnPDFHasUnsupportedFeature) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP_EX() @@ -1778,6 +1783,15 @@ void TabContents::OnDidFinishLoad(int64 frame_id) { Details<int64>(&frame_id)); } +void TabContents::OnUpdateContentRestrictions(int restrictions) { + content_restrictions_ = restrictions; + delegate()->ContentRestrictionsChanged(this); +} + +void TabContents::OnPDFHasUnsupportedFeature() { + PDFHasUnsupportedFeature(this); +} + // Notifies the RenderWidgetHost instance about the fact that the page is // loading, or done loading and calls the base implementation. void TabContents::SetIsLoading(bool is_loading, @@ -3130,11 +3144,6 @@ void TabContents::UpdateZoomLimits(int minimum_percent, temporary_zoom_settings_ = !remember; } -void TabContents::UpdateContentRestrictions(int restrictions) { - content_restrictions_ = restrictions; - delegate()->ContentRestrictionsChanged(this); -} - void TabContents::BeforeUnloadFiredFromRenderManager( bool proceed, bool* proceed_to_fire_unload) { diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h index 1da5f14..da61f4e 100644 --- a/chrome/browser/tab_contents/tab_contents.h +++ b/chrome/browser/tab_contents/tab_contents.h @@ -808,6 +808,8 @@ class TabContents : public PageNavigator, const GURL& target_url); void OnDocumentLoadedInFrame(int64 frame_id); void OnDidFinishLoad(int64 frame_id); + void OnUpdateContentRestrictions(int restrictions); + void OnPDFHasUnsupportedFeature(); // Changes the IsLoading state and notifies delegate as needed // |details| is used to provide details on the load that just finished @@ -1033,7 +1035,6 @@ class TabContents : public PageNavigator, virtual void UpdateZoomLimits(int minimum_percent, int maximum_percent, bool remember); - virtual void UpdateContentRestrictions(int restrictions); // RenderViewHostManager::Delegate ------------------------------------------- diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index dc048d7..7a1ea50 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1330,6 +1330,8 @@ 'browser/gpu_process_host.h', 'browser/gpu_process_host_ui_shim.cc', 'browser/gpu_process_host_ui_shim.h', + 'browser/pdf_unsupported_feature.cc', + 'browser/pdf_unsupported_feature.h', 'browser/ui/gtk/about_chrome_dialog.cc', 'browser/ui/gtk/about_chrome_dialog.h', 'browser/ui/gtk/accelerators_gtk.cc', diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index e3f8af1..d8f3fb6 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -1393,7 +1393,8 @@ IPC_SYNC_MESSAGE_CONTROL1_1(ViewHostMsg_GetPlugins, // |actual_mime_type| is the actual mime type supported by the // plugin found that match the URL given (one for each item in // |info|). -IPC_SYNC_MESSAGE_CONTROL3_4(ViewHostMsg_GetPluginInfo, +IPC_SYNC_MESSAGE_CONTROL4_4(ViewHostMsg_GetPluginInfo, + int /* routing_id */, GURL /* url */, GURL /* policy_url */, std::string /* mime_type */, @@ -1538,7 +1539,8 @@ IPC_MESSAGE_ROUTED3(ViewHostMsg_ForwardMessageToExternalHost, // create a plugin. The browser will create the plugin process if // necessary, and will return a handle to the channel on success. // On error an empty string is returned. -IPC_SYNC_MESSAGE_CONTROL2_2(ViewHostMsg_OpenChannelToPlugin, +IPC_SYNC_MESSAGE_CONTROL3_2(ViewHostMsg_OpenChannelToPlugin, + int /* routing_id */, GURL /* url */, std::string /* mime_type */, IPC::ChannelHandle /* channel_handle */, @@ -2587,6 +2589,9 @@ IPC_MESSAGE_ROUTED2(ViewHostMsg_ScriptEvalResponse, IPC_MESSAGE_ROUTED1(ViewHostMsg_UpdateContentRestrictions, int /* restrictions */) +// The currently displayed PDF has an unsupported feature. +IPC_MESSAGE_ROUTED0(ViewHostMsg_PDFHasUnsupportedFeature) + // Pepper-related messages ----------------------------------------------------- IPC_MESSAGE_CONTROL4(ViewHostMsg_PepperConnectTcp, diff --git a/chrome/renderer/pepper_plugin_delegate_impl.cc b/chrome/renderer/pepper_plugin_delegate_impl.cc index f4c2923..87b9914 100644 --- a/chrome/renderer/pepper_plugin_delegate_impl.cc +++ b/chrome/renderer/pepper_plugin_delegate_impl.cc @@ -891,3 +891,8 @@ void PepperPluginDelegateImpl::SetContentRestriction(int restrictions) { render_view_->Send(new ViewHostMsg_UpdateContentRestrictions( render_view_->routing_id(), restrictions)); } + +void PepperPluginDelegateImpl::HasUnsupportedFeature() { + render_view_->Send(new ViewHostMsg_PDFHasUnsupportedFeature( + render_view_->routing_id())); +} diff --git a/chrome/renderer/pepper_plugin_delegate_impl.h b/chrome/renderer/pepper_plugin_delegate_impl.h index b4d7424..408163a 100644 --- a/chrome/renderer/pepper_plugin_delegate_impl.h +++ b/chrome/renderer/pepper_plugin_delegate_impl.h @@ -163,6 +163,7 @@ class PepperPluginDelegateImpl virtual void DidStartLoading(); virtual void DidStopLoading(); virtual void SetContentRestriction(int restrictions); + virtual void HasUnsupportedFeature(); private: // Pointer to the RenderView that owns us. diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index ff9a209..d8adaaa 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -918,8 +918,8 @@ WebPlugin* RenderView::CreatePluginNoCheck(WebFrame* frame, ContentSetting setting; std::string mime_type; Send(new ViewHostMsg_GetPluginInfo( - params.url, frame->top()->url(), params.mimeType.utf8(), &found, - &info, &setting, &mime_type)); + routing_id_, params.url, frame->top()->url(), params.mimeType.utf8(), + &found, &info, &setting, &mime_type)); if (!found || !info.enabled) return NULL; @@ -2687,7 +2687,8 @@ WebPlugin* RenderView::createPlugin(WebFrame* frame, webkit::npapi::WebPluginInfo info; GURL url(params.url); std::string actual_mime_type; - Send(new ViewHostMsg_GetPluginInfo(url, + Send(new ViewHostMsg_GetPluginInfo(routing_id_, + url, frame->top()->url(), params.mimeType.utf8(), &found, diff --git a/chrome/renderer/webplugin_delegate_proxy.cc b/chrome/renderer/webplugin_delegate_proxy.cc index e5c50ec..6e7b4fd 100644 --- a/chrome/renderer/webplugin_delegate_proxy.cc +++ b/chrome/renderer/webplugin_delegate_proxy.cc @@ -274,7 +274,8 @@ bool WebPluginDelegateProxy::Initialize( bool load_manually) { IPC::ChannelHandle channel_handle; if (!RenderThread::current()->Send(new ViewHostMsg_OpenChannelToPlugin( - url, mime_type_, &channel_handle, &info_))) { + render_view_->routing_id(), url, mime_type_, &channel_handle, + &info_))) { return false; } diff --git a/chrome/tools/chromeactions.txt b/chrome/tools/chromeactions.txt index 675a4e2..e1d450f 100644 --- a/chrome/tools/chromeactions.txt +++ b/chrome/tools/chromeactions.txt @@ -947,6 +947,17 @@ 0x99ed44568e5ff51a PDF.ZoomFromBrowser 0x1d57434947820665 PDF.ZoomInButton 0x40ca10ced9d81d17 PDF.ZoomOutButton +0x0b951f22f9dff291 PDF_Unsupported_3D +0xd39cd7449ac8702e PDF_Unsupported_Attachments +0x922a34409936d3c6 PDF_Unsupported_Digital_Signatures +0xc09901d647ad722f PDF_Unsupported_Movie +0x282157da06d467b9 PDF_Unsupported_Portfolios +0x74d8faa1b4e92791 PDF_Unsupported_Rights_Management +0x571f65a8518214ae PDF_Unsupported_Screen +0xdf8b4231d4bd3f90 PDF_Unsupported_Shared_Form +0xb7474a7496f9a77e PDF_Unsupported_Shared_Review +0x6efe497913838ea5 PDF_Unsupported_Sound +0x08640e2d5578cf94 PDF_Unsupported_XFA 0xee3677bcca83ece9 PageDown 0x9b869c510c75c582 PageUp 0x9ba3ff80fde405cd PasswordManager_Disabled diff --git a/chrome/tools/extract_actions.py b/chrome/tools/extract_actions.py index e28c85b..b111182 100755 --- a/chrome/tools/extract_actions.py +++ b/chrome/tools/extract_actions.py @@ -162,6 +162,17 @@ def AddClosedSourceActions(actions): actions.add('PDF.ZoomFromBrowser') actions.add('PDF.ZoomOutButton') actions.add('PDF.ZoomInButton') + actions.add('PDF_Unsupported_Rights_Management') + actions.add('PDF_Unsupported_XFA') + actions.add('PDF_Unsupported_3D') + actions.add('PDF_Unsupported_Movie') + actions.add('PDF_Unsupported_Sound') + actions.add('PDF_Unsupported_Screen') + actions.add('PDF_Unsupported_Portfolios') + actions.add('PDF_Unsupported_Attachments') + actions.add('PDF_Unsupported_Digital_Signatures') + actions.add('PDF_Unsupported_Shared_Review') + actions.add('PDF_Unsupported_Shared_Form') def AddAboutFlagsActions(actions): """This parses the experimental feature flags for UMA actions. diff --git a/ppapi/c/private/ppb_pdf.h b/ppapi/c/private/ppb_pdf.h index cdc384a..2f16ca4 100644 --- a/ppapi/c/private/ppb_pdf.h +++ b/ppapi/c/private/ppb_pdf.h @@ -130,6 +130,9 @@ struct PPB_PDF { // Notifies the browser that the given action has been performed. void (*UserMetricsRecordAction)(PP_Var action); + + // Notifies the browser that the PDF has an unsupported feature. + void (*HasUnsupportedFeature)(PP_Instance instance); }; #endif // PPAPI_C_PRIVATE_PPB_PDF_H_ diff --git a/webkit/plugins/npapi/plugin_group.cc b/webkit/plugins/npapi/plugin_group.cc index 6323998..dd80652 100644 --- a/webkit/plugins/npapi/plugin_group.cc +++ b/webkit/plugins/npapi/plugin_group.cc @@ -17,6 +17,7 @@ namespace webkit { namespace npapi { const char* PluginGroup::kAdobeReaderGroupName = "Adobe Reader"; +const char* PluginGroup::kAdobeReaderUpdateURL = "http://get.adobe.com/reader/"; /*static*/ std::set<string16>* PluginGroup::policy_disabled_plugin_patterns_; diff --git a/webkit/plugins/npapi/plugin_group.h b/webkit/plugins/npapi/plugin_group.h index 308fee7..d6a2157 100644 --- a/webkit/plugins/npapi/plugin_group.h +++ b/webkit/plugins/npapi/plugin_group.h @@ -78,6 +78,7 @@ class PluginGroup { // Used by about:plugins to disable Reader plugin when internal PDF viewer is // enabled. static const char* kAdobeReaderGroupName; + static const char* kAdobeReaderUpdateURL; PluginGroup(const PluginGroup& other); @@ -145,6 +146,8 @@ class PluginGroup { // in accepting weird version strings than Version::GetFromString(). static Version* CreateVersionFromString(const string16& version_string); + std::vector<WebPluginInfo> web_plugin_infos() { return web_plugin_infos_; } + private: typedef std::map<std::string, PluginGroup*> PluginMap; diff --git a/webkit/plugins/ppapi/mock_plugin_delegate.cc b/webkit/plugins/ppapi/mock_plugin_delegate.cc index 2a96571..e072ebb 100644 --- a/webkit/plugins/ppapi/mock_plugin_delegate.cc +++ b/webkit/plugins/ppapi/mock_plugin_delegate.cc @@ -199,6 +199,9 @@ void MockPluginDelegate::DidStopLoading() { void MockPluginDelegate::SetContentRestriction(int restrictions) { } +void MockPluginDelegate::HasUnsupportedFeature() { +} + } // namespace ppapi } // namespace webkit diff --git a/webkit/plugins/ppapi/mock_plugin_delegate.h b/webkit/plugins/ppapi/mock_plugin_delegate.h index 7037a96..5d652dd 100644 --- a/webkit/plugins/ppapi/mock_plugin_delegate.h +++ b/webkit/plugins/ppapi/mock_plugin_delegate.h @@ -99,6 +99,7 @@ class MockPluginDelegate : public PluginDelegate { virtual void DidStartLoading(); virtual void DidStopLoading(); virtual void SetContentRestriction(int restrictions); + virtual void HasUnsupportedFeature(); }; } // namespace ppapi diff --git a/webkit/plugins/ppapi/plugin_delegate.h b/webkit/plugins/ppapi/plugin_delegate.h index fd73c3a..1a94ea0 100644 --- a/webkit/plugins/ppapi/plugin_delegate.h +++ b/webkit/plugins/ppapi/plugin_delegate.h @@ -335,6 +335,9 @@ class PluginDelegate { // Sets restrictions on how the content can be used (i.e. no print/copy). virtual void SetContentRestriction(int restrictions) = 0; + + // Tells the browser that the PDF has an unsupported feature. + virtual void HasUnsupportedFeature() = 0; }; } // namespace ppapi diff --git a/webkit/plugins/ppapi/ppapi_plugin_instance.cc b/webkit/plugins/ppapi/ppapi_plugin_instance.cc index dfe1456..8a9f62a 100644 --- a/webkit/plugins/ppapi/ppapi_plugin_instance.cc +++ b/webkit/plugins/ppapi/ppapi_plugin_instance.cc @@ -250,9 +250,8 @@ void ZoomChanged(PP_Instance instance_id, double factor) { return; // We only want to tell the page to change its zoom if the whole page is the - // PDF. If we're in an iframe, then don't do anything. - WebFrame* frame = instance->container()->element().document().frame(); - if (!frame->view()->mainFrame()->document().isPluginDocument()) + // plugin. If we're in an iframe, then don't do anything. + if (!instance->IsFullPagePlugin()) return; double zoom_level = WebView::zoomFactorToZoomLevel(factor); @@ -1231,5 +1230,10 @@ ObjectVar* PluginInstance::ObjectVarForNPObject(NPObject* np_object) const { return found->second; } +bool PluginInstance::IsFullPagePlugin() const { + WebFrame* frame = container()->element().document().frame(); + return frame->view()->mainFrame()->document().isPluginDocument(); +} + } // namespace ppapi } // namespace webkit diff --git a/webkit/plugins/ppapi/ppapi_plugin_instance.h b/webkit/plugins/ppapi/ppapi_plugin_instance.h index ec1bf9c..f80402f 100644 --- a/webkit/plugins/ppapi/ppapi_plugin_instance.h +++ b/webkit/plugins/ppapi/ppapi_plugin_instance.h @@ -203,6 +203,10 @@ class PluginInstance : public base::RefCounted<PluginInstance> { // NPObject for the given module. See AddNPObjectVar above. ObjectVar* ObjectVarForNPObject(NPObject* np_object) const; + // Returns true iff the plugin is a full-page plugin (i.e. not in an iframe or + // embedded in a page). + bool IsFullPagePlugin() const; + private: bool LoadFindInterface(); bool LoadPdfInterface(); diff --git a/webkit/plugins/ppapi/ppb_pdf_impl.cc b/webkit/plugins/ppapi/ppb_pdf_impl.cc index cf0d1d0..2019945 100644 --- a/webkit/plugins/ppapi/ppb_pdf_impl.cc +++ b/webkit/plugins/ppapi/ppb_pdf_impl.cc @@ -268,6 +268,18 @@ void UserMetricsRecordAction(PP_Var action) { webkit_glue::UserMetricsRecordAction(action_str->value()); } +void HasUnsupportedFeature(PP_Instance instance_id) { + PluginInstance* instance = ResourceTracker::Get()->GetInstance(instance_id); + if (!instance) + return; + + // Only want to show an info bar if the pdf is the whole tab. + if (!instance->IsFullPagePlugin()) + return; + + instance->delegate()->HasUnsupportedFeature(); +} + const PPB_PDF ppb_pdf = { &GetLocalizedString, &GetResourceImage, @@ -278,7 +290,8 @@ const PPB_PDF ppb_pdf = { &DidStopLoading, &SetContentRestriction, &HistogramPDFPageCount, - &UserMetricsRecordAction + &UserMetricsRecordAction, + &HasUnsupportedFeature }; } // namespace |