blob: 2eb7f21919fdc631c546e934320dc85fa7503a1c [file] [log] [blame]
// Copyright 2017 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/plugins/pdf_iframe_navigation_throttle.h"
#include <string>
#include "base/feature_list.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/task/post_task.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pdf_util.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_utils.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
#include "net/base/escape.h"
#include "net/http/http_response_headers.h"
#if BUILDFLAG(ENABLE_PLUGINS)
#include "chrome/browser/plugins/chrome_plugin_service_filter.h"
#include "content/public/browser/plugin_service.h"
#endif
namespace {
// Used to scope the posted navigation task to the lifetime of |web_contents|.
class PdfWebContentsLifetimeHelper
: public content::WebContentsUserData<PdfWebContentsLifetimeHelper> {
public:
explicit PdfWebContentsLifetimeHelper(content::WebContents* web_contents)
: web_contents_(web_contents) {}
base::WeakPtr<PdfWebContentsLifetimeHelper> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void NavigateIFrameToPlaceholder(const content::OpenURLParams& url_params) {
web_contents_->OpenURL(url_params);
}
private:
friend class content::WebContentsUserData<PdfWebContentsLifetimeHelper>;
content::WebContents* const web_contents_;
base::WeakPtrFactory<PdfWebContentsLifetimeHelper> weak_factory_{this};
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
WEB_CONTENTS_USER_DATA_KEY_IMPL(PdfWebContentsLifetimeHelper)
#if BUILDFLAG(ENABLE_PLUGINS)
// Returns true if the PDF plugin for |navigation_handle| is enabled. Optionally
// also sets |is_stale| to true if the plugin list needs a reload.
bool IsPDFPluginEnabled(content::NavigationHandle* navigation_handle,
bool* is_stale) {
content::WebContents* web_contents = navigation_handle->GetWebContents();
int process_id = web_contents->GetMainFrame()->GetProcess()->GetID();
int routing_id = web_contents->GetMainFrame()->GetRoutingID();
content::ResourceContext* resource_context =
web_contents->GetBrowserContext()->GetResourceContext();
content::WebPluginInfo plugin_info;
return content::PluginService::GetInstance()->GetPluginInfo(
process_id, routing_id, resource_context, navigation_handle->GetURL(),
web_contents->GetMainFrame()->GetLastCommittedOrigin(), kPDFMimeType,
false /* allow_wildcard */, is_stale, &plugin_info,
nullptr /* actual_mime_type */);
}
#endif
} // namespace
PDFIFrameNavigationThrottle::PDFIFrameNavigationThrottle(
content::NavigationHandle* navigation_handle)
: content::NavigationThrottle(navigation_handle) {}
PDFIFrameNavigationThrottle::~PDFIFrameNavigationThrottle() {}
const char* PDFIFrameNavigationThrottle::GetNameForLogging() {
return "PDFIFrameNavigationThrottle";
}
// static
std::unique_ptr<content::NavigationThrottle>
PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(
content::NavigationHandle* handle) {
if (handle->IsInMainFrame())
return nullptr;
return std::make_unique<PDFIFrameNavigationThrottle>(handle);
}
content::NavigationThrottle::ThrottleCheckResult
PDFIFrameNavigationThrottle::WillProcessResponse() {
const net::HttpResponseHeaders* response_headers =
navigation_handle()->GetResponseHeaders();
if (!response_headers)
return content::NavigationThrottle::PROCEED;
std::string mime_type;
response_headers->GetMimeType(&mime_type);
if (mime_type != kPDFMimeType)
return content::NavigationThrottle::PROCEED;
// We MUST download responses marked as attachments rather than showing
// a placeholder.
if (content::download_utils::MustDownload(navigation_handle()->GetURL(),
response_headers, mime_type)) {
return content::NavigationThrottle::PROCEED;
}
ReportPDFLoadStatus(PDFLoadStatus::kLoadedIframePdfWithNoPdfViewer);
if (!base::FeatureList::IsEnabled(features::kClickToOpenPDFPlaceholder))
return content::NavigationThrottle::PROCEED;
#if BUILDFLAG(ENABLE_PLUGINS)
bool is_stale = false;
bool pdf_plugin_enabled = IsPDFPluginEnabled(navigation_handle(), &is_stale);
if (is_stale) {
// On browser start, the plugin list may not be ready yet.
content::PluginService::GetInstance()->GetPlugins(
base::BindOnce(&PDFIFrameNavigationThrottle::OnPluginsLoaded,
weak_factory_.GetWeakPtr()));
return content::NavigationThrottle::DEFER;
}
// If the plugin was found, proceed on the navigation. Otherwise fall through
// to the placeholder case.
if (pdf_plugin_enabled)
return content::NavigationThrottle::PROCEED;
#endif
LoadPlaceholderHTML();
return content::NavigationThrottle::CANCEL_AND_IGNORE;
}
#if BUILDFLAG(ENABLE_PLUGINS)
void PDFIFrameNavigationThrottle::OnPluginsLoaded(
const std::vector<content::WebPluginInfo>& plugins) {
if (IsPDFPluginEnabled(navigation_handle(), nullptr /* is_stale */)) {
Resume();
} else {
LoadPlaceholderHTML();
CancelDeferredNavigation(content::NavigationThrottle::CANCEL_AND_IGNORE);
}
}
#endif
void PDFIFrameNavigationThrottle::LoadPlaceholderHTML() {
// Prepare the params to navigate to the placeholder.
std::string html = GetPDFPlaceholderHTML(navigation_handle()->GetURL());
GURL data_url("data:text/html," + net::EscapePath(html));
content::OpenURLParams params(data_url, navigation_handle()->GetReferrer(),
navigation_handle()->GetFrameTreeNodeId(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_AUTO_SUBFRAME,
navigation_handle()->IsRendererInitiated());
params.initiator_origin = navigation_handle()->GetInitiatorOrigin();
// Post a task to navigate to the placeholder HTML. We don't navigate
// synchronously here, as starting a navigation within a navigation is
// an antipattern. Use a helper object scoped to the WebContents lifetime to
// scope the navigation task to the WebContents lifetime.
content::WebContents* web_contents = navigation_handle()->GetWebContents();
PdfWebContentsLifetimeHelper::CreateForWebContents(web_contents);
PdfWebContentsLifetimeHelper* helper =
PdfWebContentsLifetimeHelper::FromWebContents(web_contents);
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&PdfWebContentsLifetimeHelper::NavigateIFrameToPlaceholder,
helper->GetWeakPtr(), params));
}