blob: a3acac2e0a8b859acbb81a2e06f8096764022dda [file] [log] [blame]
// 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/printing/print_view_manager.h"
#include <map>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/lazy_instance.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/bad_message.h"
#include "chrome/browser/printing/print_preview_dialog_controller.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
#include "content/public/browser/browser_thread.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/web_contents.h"
#include "ipc/ipc_message_macros.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "printing/buildflags/buildflags.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
#endif
using content::BrowserThread;
namespace printing {
namespace {
PrintManager* g_receiver_for_testing = nullptr;
// Keeps track of pending scripted print preview closures.
// No locking, only access on the UI thread.
base::LazyInstance<std::map<content::RenderProcessHost*, base::OnceClosure>>::
Leaky g_scripted_print_preview_closure_map = LAZY_INSTANCE_INITIALIZER;
content::WebContents* GetPrintPreviewDialog(
content::WebContents* web_contents) {
PrintPreviewDialogController* dialog_controller =
PrintPreviewDialogController::GetInstance();
if (!dialog_controller)
return nullptr;
return dialog_controller->GetPrintPreviewForContents(web_contents);
}
} // namespace
PrintViewManager::PrintViewManager(content::WebContents* web_contents)
: PrintViewManagerBase(web_contents),
content::WebContentsUserData<PrintViewManager>(*web_contents) {}
PrintViewManager::~PrintViewManager() {
DCHECK_EQ(NOT_PREVIEWING, print_preview_state_);
}
// static
void PrintViewManager::BindPrintManagerHost(
mojo::PendingAssociatedReceiver<mojom::PrintManagerHost> receiver,
content::RenderFrameHost* rfh) {
if (g_receiver_for_testing) {
g_receiver_for_testing->BindReceiver(std::move(receiver), rfh);
return;
}
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
if (!web_contents)
return;
auto* print_manager = PrintViewManager::FromWebContents(web_contents);
if (!print_manager)
return;
print_manager->BindReceiver(std::move(receiver), rfh);
}
bool PrintViewManager::PrintForSystemDialogNow(
base::OnceClosure dialog_shown_callback) {
DCHECK(dialog_shown_callback);
DCHECK(!on_print_dialog_shown_callback_);
on_print_dialog_shown_callback_ = std::move(dialog_shown_callback);
is_switching_to_system_dialog_ = true;
// Remember the ID for `print_preview_rfh_`, to enable checking that the
// `RenderFrameHost` is still valid after a possible inner message loop runs
// in `DisconnectFromCurrentPrintJob()`.
content::GlobalRenderFrameHostId rfh_id = print_preview_rfh_->GetGlobalId();
auto weak_this = weak_factory_.GetWeakPtr();
DisconnectFromCurrentPrintJob();
if (!weak_this)
return false;
// Don't print / print preview crashed tabs.
if (IsCrashed())
return false;
// Don't print if `print_preview_rfh_` is no longer live.
if (!content::RenderFrameHost::FromID(rfh_id) ||
!print_preview_rfh_->IsRenderFrameLive()) {
return false;
}
// TODO(crbug.com/809738) Register with `PrintBackendServiceManager` when
// system print is enabled out-of-process.
SetPrintingRFH(print_preview_rfh_);
GetPrintRenderFrame(print_preview_rfh_)->PrintForSystemDialog();
return true;
}
bool PrintViewManager::BasicPrint(content::RenderFrameHost* rfh) {
PrintPreviewDialogController* dialog_controller =
PrintPreviewDialogController::GetInstance();
if (!dialog_controller)
return false;
content::WebContents* print_preview_dialog =
dialog_controller->GetPrintPreviewForContents(web_contents());
if (!print_preview_dialog)
return PrintNow(rfh);
return !!print_preview_dialog->GetWebUI();
}
bool PrintViewManager::PrintPreviewNow(content::RenderFrameHost* rfh,
bool has_selection) {
return PrintPreview(rfh, mojo::NullAssociatedRemote(), has_selection);
}
bool PrintViewManager::PrintPreviewWithPrintRenderer(
content::RenderFrameHost* rfh,
mojo::PendingAssociatedRemote<mojom::PrintRenderer> print_renderer) {
return PrintPreview(rfh, std::move(print_renderer), false);
}
void PrintViewManager::PrintPreviewForWebNode(content::RenderFrameHost* rfh) {
if (print_preview_state_ != NOT_PREVIEWING)
return;
DCHECK(rfh);
DCHECK(IsPrintRenderFrameConnected(rfh));
// All callers should already ensure this condition holds; CHECK to
// aggressively protect against future unsafety.
CHECK(rfh->IsRenderFrameLive());
DCHECK(!print_preview_rfh_);
print_preview_rfh_ = rfh;
print_preview_state_ = USER_INITIATED_PREVIEW;
for (auto& observer : GetObservers())
observer.OnPrintPreview(print_preview_rfh_);
}
void PrintViewManager::PrintPreviewAlmostDone() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (print_preview_state_ != SCRIPTED_PREVIEW)
return;
MaybeUnblockScriptedPreviewRPH();
}
void PrintViewManager::PrintPreviewDone() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (print_preview_state_ == NOT_PREVIEWING)
return;
// Send OnPrintPreviewDialogClosed message for 'afterprint' event.
#if defined(OS_WIN)
// On Windows, we always send OnPrintPreviewDialogClosed. It's ok to dispatch
// 'afterprint' at this timing because system dialog printing on
// Windows doesn't need the original frame.
bool send_message = true;
#else
// On non-Windows, we don't need to send OnPrintPreviewDialogClosed when we
// are switching to system dialog. PrintRenderFrameHelper is responsible to
// dispatch 'afterprint' event.
bool send_message = !is_switching_to_system_dialog_;
#endif
if (send_message) {
// Only send a message about having closed if the RenderFrame is live and
// PrintRenderFrame is connected. Normally IsPrintRenderFrameConnected()
// implies IsRenderFrameLive(). However, when a renderer process exits
// (e.g. due to a crash), RenderFrameDeleted() and PrintPreviewDone() are
// triggered by independent observers. Since there is no guarantee which
// observer will run first, both conditions are explicitly checked here.
if (print_preview_rfh_->IsRenderFrameLive() &&
IsPrintRenderFrameConnected(print_preview_rfh_)) {
GetPrintRenderFrame(print_preview_rfh_)->OnPrintPreviewDialogClosed();
}
}
is_switching_to_system_dialog_ = false;
if (print_preview_state_ == SCRIPTED_PREVIEW) {
auto& map = g_scripted_print_preview_closure_map.Get();
auto it = map.find(scripted_print_preview_rph_);
CHECK(it != map.end());
std::move(it->second).Run();
map.erase(it);
// PrintPreviewAlmostDone() usually already calls this. Calling it again
// will likely be a no-op, but do it anyway to reset the state for sure.
MaybeUnblockScriptedPreviewRPH();
scripted_print_preview_rph_ = nullptr;
}
print_preview_state_ = NOT_PREVIEWING;
print_preview_rfh_ = nullptr;
}
void PrintViewManager::RejectPrintPreviewRequestIfRestricted(
base::OnceCallback<void(bool should_proceed)> callback) {
#if defined(OS_CHROMEOS)
// Don't print DLP restricted content on Chrome OS.
policy::DlpContentManager::Get()->CheckPrintingRestriction(
web_contents(), std::move(callback));
#else
std::move(callback).Run(true);
#endif
}
void PrintViewManager::OnPrintPreviewRequestRejected(int render_process_id,
int render_frame_id) {
auto* rfh =
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
if (!rfh) {
return;
}
PrintPreviewDone();
PrintPreviewRejectedForTesting();
}
void PrintViewManager::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (render_frame_host == print_preview_rfh_)
PrintPreviewDone();
PrintViewManagerBase::RenderFrameDeleted(render_frame_host);
}
// static
void PrintViewManager::SetReceiverImplForTesting(PrintManager* impl) {
g_receiver_for_testing = impl;
}
bool PrintViewManager::PrintPreview(
content::RenderFrameHost* rfh,
mojo::PendingAssociatedRemote<mojom::PrintRenderer> print_renderer,
bool has_selection) {
// Users can send print commands all they want and it is beyond
// PrintViewManager's control. Just ignore the extra commands.
// See http://crbug.com/136842 for example.
if (print_preview_state_ != NOT_PREVIEWING)
return false;
// Don't print / print preview crashed tabs.
if (IsCrashed() || !rfh->IsRenderFrameLive())
return false;
GetPrintRenderFrame(rfh)->InitiatePrintPreview(std::move(print_renderer),
has_selection);
DCHECK(!print_preview_rfh_);
print_preview_rfh_ = rfh;
print_preview_state_ = USER_INITIATED_PREVIEW;
for (auto& observer : GetObservers())
observer.OnPrintPreview(print_preview_rfh_);
return true;
}
void PrintViewManager::DidShowPrintDialog() {
if (GetCurrentTargetFrame() != print_preview_rfh_)
return;
if (on_print_dialog_shown_callback_)
std::move(on_print_dialog_shown_callback_).Run();
}
void PrintViewManager::SetupScriptedPrintPreview(
SetupScriptedPrintPreviewCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
content::RenderFrameHost* rfh = GetCurrentTargetFrame();
// The Mojo receiver endpoint is owned by a RenderFrameHostReceiverSet, so
// this DCHECK should always hold.
DCHECK(rfh->IsRenderFrameLive());
content::RenderProcessHost* rph = rfh->GetProcess();
if (rfh->IsNestedWithinFencedFrame()) {
// The renderer should have checked and disallowed the request for fenced
// frames in ChromeClient. Ignore the request and mark it as bad if it
// didn't happen for some reason.
bad_message::ReceivedBadMessage(
rph, bad_message::PVM_SCRIPTED_PRINT_FENCED_FRAME);
std::move(callback).Run();
return;
}
auto& map = g_scripted_print_preview_closure_map.Get();
if (base::Contains(map, rph)) {
// Renderer already handling window.print(). Abort this attempt to prevent
// the renderer from having multiple nested loops. If multiple nested loops
// existed, then they have to exit in the right order and that is messy.
std::move(callback).Run();
return;
}
if (print_preview_state_ != NOT_PREVIEWING) {
// If a print dialog is already open for this tab, ignore the scripted print
// message.
std::move(callback).Run();
return;
}
PrintPreviewDialogController* dialog_controller =
PrintPreviewDialogController::GetInstance();
if (!dialog_controller) {
std::move(callback).Run();
return;
}
DCHECK(!print_preview_rfh_);
print_preview_rfh_ = rfh;
print_preview_state_ = SCRIPTED_PREVIEW;
map[rph] = base::BindOnce(&PrintViewManager::OnScriptedPrintPreviewReply,
base::Unretained(this), std::move(callback));
scripted_print_preview_rph_ = rph;
DCHECK(!scripted_print_preview_rph_set_blocked_);
if (!scripted_print_preview_rph_->IsBlocked()) {
scripted_print_preview_rph_->SetBlocked(true);
scripted_print_preview_rph_set_blocked_ = true;
}
}
void PrintViewManager::ShowScriptedPrintPreview(bool source_is_modifiable) {
if (print_preview_state_ != SCRIPTED_PREVIEW)
return;
DCHECK(print_preview_rfh_);
if (GetCurrentTargetFrame() != print_preview_rfh_)
return;
int render_process_id = print_preview_rfh_->GetProcess()->GetID();
int render_frame_id = print_preview_rfh_->GetRoutingID();
RejectPrintPreviewRequestIfRestricted(
base::BindOnce(&PrintViewManager::OnScriptedPrintPreviewCallback,
weak_factory_.GetWeakPtr(), source_is_modifiable,
render_process_id, render_frame_id));
}
void PrintViewManager::OnScriptedPrintPreviewCallback(bool source_is_modifiable,
int render_process_id,
int render_frame_id,
bool should_proceed) {
if (!should_proceed) {
OnPrintPreviewRequestRejected(render_process_id, render_frame_id);
return;
}
if (print_preview_state_ != SCRIPTED_PREVIEW)
return;
DCHECK(print_preview_rfh_);
auto* rfh =
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
if (!rfh || rfh != print_preview_rfh_) {
return;
}
PrintPreviewDialogController* dialog_controller =
PrintPreviewDialogController::GetInstance();
if (!dialog_controller) {
PrintPreviewDone();
return;
}
// Running a dialog causes an exit to webpage-initiated fullscreen.
// http://crbug.com/728276
if (web_contents()->IsFullscreen())
web_contents()->ExitFullscreen(true);
dialog_controller->PrintPreview(web_contents());
mojom::RequestPrintPreviewParams params;
params.is_modifiable = source_is_modifiable;
PrintPreviewUI::SetInitialParams(
dialog_controller->GetPrintPreviewForContents(web_contents()), params);
PrintPreviewAllowedForTesting();
}
void PrintViewManager::RequestPrintPreview(
mojom::RequestPrintPreviewParamsPtr params) {
content::RenderFrameHost* render_frame_host = GetCurrentTargetFrame();
content::RenderProcessHost* render_process_host =
render_frame_host->GetProcess();
RejectPrintPreviewRequestIfRestricted(base::BindOnce(
&PrintViewManager::OnRequestPrintPreviewCallback,
weak_factory_.GetWeakPtr(), std::move(params),
render_process_host->GetID(), render_frame_host->GetRoutingID()));
}
void PrintViewManager::OnRequestPrintPreviewCallback(
mojom::RequestPrintPreviewParamsPtr params,
int render_process_id,
int render_frame_id,
bool should_proceed) {
if (!should_proceed) {
OnPrintPreviewRequestRejected(render_process_id, render_frame_id);
return;
}
// Double-check that the RenderFrameHost is still alive and has a live
// RenderFrame, since the DLP check is potentially asynchronous.
auto* render_frame_host =
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
if (!render_frame_host || !render_frame_host->IsRenderFrameLive()) {
return;
}
if (params->webnode_only) {
PrintPreviewForWebNode(render_frame_host);
}
PrintPreviewDialogController::PrintPreview(web_contents());
PrintPreviewUI::SetInitialParams(GetPrintPreviewDialog(web_contents()),
*params);
PrintPreviewAllowedForTesting();
}
void PrintViewManager::CheckForCancel(int32_t preview_ui_id,
int32_t request_id,
CheckForCancelCallback callback) {
std::move(callback).Run(
PrintPreviewUI::ShouldCancelRequest(preview_ui_id, request_id));
}
void PrintViewManager::OnScriptedPrintPreviewReply(
SetupScriptedPrintPreviewCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback).Run();
}
void PrintViewManager::MaybeUnblockScriptedPreviewRPH() {
if (scripted_print_preview_rph_set_blocked_) {
scripted_print_preview_rph_->SetBlocked(false);
scripted_print_preview_rph_set_blocked_ = false;
}
}
void PrintViewManager::PrintPreviewRejectedForTesting() {
// Note: This is only used for testing.
}
void PrintViewManager::PrintPreviewAllowedForTesting() {
// Note: This is only used for testing.
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(PrintViewManager);
} // namespace printing