mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2024-12-16 19:39:18 +01:00
573 lines
19 KiB
C++
573 lines
19 KiB
C++
// Copyright 2013 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 "libcef/browser/printing/print_view_manager_base.h"
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "base/auto_reset.h"
|
|
#include "base/bind.h"
|
|
#include "base/location.h"
|
|
#include "base/memory/ref_counted_memory.h"
|
|
#include "base/memory/shared_memory.h"
|
|
#include "base/message_loop/message_loop_current.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/single_thread_task_runner.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/task_scheduler/post_task.h"
|
|
#include "base/threading/thread_task_runner_handle.h"
|
|
#include "base/timer/timer.h"
|
|
#include "build/build_config.h"
|
|
#include "chrome/browser/browser_process.h"
|
|
#include "chrome/browser/chrome_notification_types.h"
|
|
#include "chrome/browser/printing/print_job.h"
|
|
#include "chrome/browser/printing/print_job_manager.h"
|
|
#include "chrome/browser/printing/printer_query.h"
|
|
#include "chrome/browser/profiles/profile.h"
|
|
#include "chrome/common/pref_names.h"
|
|
#include "chrome/common/webui_url_constants.h"
|
|
#include "chrome/grit/generated_resources.h"
|
|
#include "components/prefs/pref_service.h"
|
|
#include "components/printing/browser/print_composite_client.h"
|
|
#include "components/printing/browser/print_manager_utils.h"
|
|
#include "components/printing/common/print_messages.h"
|
|
#include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h"
|
|
#include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.h"
|
|
#include "content/public/browser/browser_thread.h"
|
|
#include "content/public/browser/notification_details.h"
|
|
#include "content/public/browser/notification_service.h"
|
|
#include "content/public/browser/notification_source.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 "extensions/common/constants.h"
|
|
#include "mojo/public/cpp/system/buffer.h"
|
|
#include "printing/buildflags/buildflags.h"
|
|
#include "printing/pdf_metafile_skia.h"
|
|
#include "printing/print_settings.h"
|
|
#include "printing/printed_document.h"
|
|
#include "ui/base/l10n/l10n_util.h"
|
|
#include "url/gurl.h"
|
|
|
|
#if defined(OS_WIN)
|
|
#include "base/command_line.h"
|
|
#include "chrome/common/chrome_features.h"
|
|
#endif
|
|
|
|
using base::TimeDelta;
|
|
using content::BrowserThread;
|
|
|
|
namespace printing {
|
|
|
|
namespace {
|
|
|
|
bool PrintingPdfContent(content::RenderFrameHost* rfh) {
|
|
GURL url = rfh->GetLastCommittedURL();
|
|
// Whether it is inside print preview or pdf plugin extension.
|
|
return url.GetOrigin() == chrome::kChromeUIPrintURL ||
|
|
(url.SchemeIs(extensions::kExtensionScheme) &&
|
|
url.host_piece() == extension_misc::kPdfExtensionId);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CefPrintViewManagerBase::CefPrintViewManagerBase(
|
|
content::WebContents* web_contents)
|
|
: PrintManager(web_contents),
|
|
printing_rfh_(nullptr),
|
|
printing_succeeded_(false),
|
|
inside_inner_message_loop_(false),
|
|
queue_(g_browser_process->print_job_manager()->queue()),
|
|
weak_ptr_factory_(this) {
|
|
DCHECK(queue_);
|
|
Profile* profile =
|
|
Profile::FromBrowserContext(web_contents->GetBrowserContext());
|
|
printing_enabled_.Init(
|
|
prefs::kPrintingEnabled, profile->GetPrefs(),
|
|
base::Bind(&CefPrintViewManagerBase::UpdatePrintingEnabled,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
}
|
|
|
|
CefPrintViewManagerBase::~CefPrintViewManagerBase() {
|
|
ReleasePrinterQuery();
|
|
DisconnectFromCurrentPrintJob();
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) {
|
|
DisconnectFromCurrentPrintJob();
|
|
|
|
SetPrintingRFH(rfh);
|
|
int32_t id = rfh->GetRoutingID();
|
|
return PrintNowInternal(rfh, std::make_unique<PrintMsg_PrintPages>(id));
|
|
}
|
|
|
|
void CefPrintViewManagerBase::PrintDocument(
|
|
const scoped_refptr<base::RefCountedMemory>& print_data,
|
|
const gfx::Size& page_size,
|
|
const gfx::Rect& content_area,
|
|
const gfx::Point& offsets) {
|
|
#if defined(OS_WIN)
|
|
print_job_->StartConversionToNativeFormat(print_data, page_size, content_area,
|
|
offsets);
|
|
#else
|
|
std::unique_ptr<PdfMetafileSkia> metafile =
|
|
std::make_unique<PdfMetafileSkia>();
|
|
CHECK(metafile->InitFromData(print_data->front(), print_data->size()));
|
|
|
|
// Update the rendered document. It will send notifications to the listener.
|
|
PrintedDocument* document = print_job_->document();
|
|
document->SetDocument(std::move(metafile), page_size, content_area);
|
|
ShouldQuitFromInnerMessageLoop();
|
|
#endif
|
|
}
|
|
|
|
void CefPrintViewManagerBase::UpdatePrintingEnabled() {
|
|
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
// The Unretained() is safe because ForEachFrame() is synchronous.
|
|
web_contents()->ForEachFrame(base::BindRepeating(
|
|
&CefPrintViewManagerBase::SendPrintingEnabled, base::Unretained(this),
|
|
printing_enabled_.GetValue()));
|
|
}
|
|
|
|
void CefPrintViewManagerBase::NavigationStopped() {
|
|
// Cancel the current job, wait for the worker to finish.
|
|
TerminatePrintJob(true);
|
|
}
|
|
|
|
base::string16 CefPrintViewManagerBase::RenderSourceName() {
|
|
base::string16 name(web_contents()->GetTitle());
|
|
if (name.empty())
|
|
name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE);
|
|
return name;
|
|
}
|
|
|
|
void CefPrintViewManagerBase::OnDidGetPrintedPagesCount(int cookie,
|
|
int number_pages) {
|
|
PrintManager::OnDidGetPrintedPagesCount(cookie, number_pages);
|
|
OpportunisticallyCreatePrintJob(cookie);
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::PrintJobHasDocument(int cookie) {
|
|
if (!OpportunisticallyCreatePrintJob(cookie))
|
|
return false;
|
|
|
|
// These checks may fail since we are completely asynchronous. Old spurious
|
|
// messages can be received if one of the processes is overloaded.
|
|
PrintedDocument* document = print_job_->document();
|
|
return document && document->cookie() == cookie;
|
|
}
|
|
|
|
void CefPrintViewManagerBase::OnComposePdfDone(
|
|
const PrintHostMsg_DidPrintDocument_Params& params,
|
|
mojom::PdfCompositor::Status status,
|
|
base::ReadOnlySharedMemoryRegion region) {
|
|
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
if (status != mojom::PdfCompositor::Status::SUCCESS) {
|
|
DLOG(ERROR) << "Compositing pdf failed with error " << status;
|
|
return;
|
|
}
|
|
|
|
if (!print_job_->document())
|
|
return;
|
|
|
|
scoped_refptr<base::RefCountedSharedMemoryMapping> data =
|
|
base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(region);
|
|
if (!data)
|
|
return;
|
|
|
|
PrintDocument(data, params.page_size, params.content_area,
|
|
params.physical_offsets);
|
|
}
|
|
|
|
void CefPrintViewManagerBase::OnDidPrintDocument(
|
|
content::RenderFrameHost* render_frame_host,
|
|
const PrintHostMsg_DidPrintDocument_Params& params) {
|
|
if (!PrintJobHasDocument(params.document_cookie))
|
|
return;
|
|
|
|
const PrintHostMsg_DidPrintContent_Params& content = params.content;
|
|
if (!base::SharedMemory::IsHandleValid(content.metafile_data_handle)) {
|
|
NOTREACHED() << "invalid memory handle";
|
|
web_contents()->Stop();
|
|
return;
|
|
}
|
|
|
|
auto* client = PrintCompositeClient::FromWebContents(web_contents());
|
|
if (IsOopifEnabled() && !PrintingPdfContent(render_frame_host)) {
|
|
client->DoCompositeDocumentToPdf(
|
|
params.document_cookie, render_frame_host, content.metafile_data_handle,
|
|
content.data_size, content.subframe_content_info,
|
|
base::BindOnce(&CefPrintViewManagerBase::OnComposePdfDone,
|
|
weak_ptr_factory_.GetWeakPtr(), params));
|
|
return;
|
|
}
|
|
auto shared_buf =
|
|
std::make_unique<base::SharedMemory>(content.metafile_data_handle, true);
|
|
if (!shared_buf->Map(content.data_size)) {
|
|
NOTREACHED() << "couldn't map";
|
|
web_contents()->Stop();
|
|
return;
|
|
}
|
|
|
|
auto data = base::MakeRefCounted<base::RefCountedSharedMemory>(
|
|
std::move(shared_buf), content.data_size);
|
|
PrintDocument(data, params.page_size, params.content_area,
|
|
params.physical_offsets);
|
|
}
|
|
|
|
void CefPrintViewManagerBase::OnPrintingFailed(int cookie) {
|
|
PrintManager::OnPrintingFailed(cookie);
|
|
|
|
ReleasePrinterQuery();
|
|
|
|
content::NotificationService::current()->Notify(
|
|
chrome::NOTIFICATION_PRINT_JOB_RELEASED,
|
|
content::Source<content::WebContents>(web_contents()),
|
|
content::NotificationService::NoDetails());
|
|
}
|
|
|
|
void CefPrintViewManagerBase::OnShowInvalidPrinterSettingsError() {}
|
|
|
|
void CefPrintViewManagerBase::DidStartLoading() {
|
|
UpdatePrintingEnabled();
|
|
}
|
|
|
|
void CefPrintViewManagerBase::RenderFrameDeleted(
|
|
content::RenderFrameHost* render_frame_host) {
|
|
// Terminates or cancels the print job if one was pending.
|
|
if (render_frame_host != printing_rfh_)
|
|
return;
|
|
|
|
printing_rfh_ = nullptr;
|
|
|
|
PrintManager::PrintingRenderFrameDeleted();
|
|
ReleasePrinterQuery();
|
|
|
|
if (!print_job_)
|
|
return;
|
|
|
|
scoped_refptr<PrintedDocument> document(print_job_->document());
|
|
if (document) {
|
|
// If IsComplete() returns false, the document isn't completely rendered.
|
|
// Since our renderer is gone, there's nothing to do, cancel it. Otherwise,
|
|
// the print job may finish without problem.
|
|
TerminatePrintJob(!document->IsComplete());
|
|
}
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::OnMessageReceived(
|
|
const IPC::Message& message,
|
|
content::RenderFrameHost* render_frame_host) {
|
|
bool handled = true;
|
|
IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(CefPrintViewManagerBase, message,
|
|
render_frame_host)
|
|
IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintDocument, OnDidPrintDocument)
|
|
IPC_MESSAGE_UNHANDLED(handled = false)
|
|
IPC_END_MESSAGE_MAP()
|
|
if (handled)
|
|
return true;
|
|
|
|
handled = true;
|
|
IPC_BEGIN_MESSAGE_MAP(CefPrintViewManagerBase, message)
|
|
IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError,
|
|
OnShowInvalidPrinterSettingsError)
|
|
IPC_MESSAGE_UNHANDLED(handled = false)
|
|
IPC_END_MESSAGE_MAP()
|
|
return handled || PrintManager::OnMessageReceived(message, render_frame_host);
|
|
}
|
|
|
|
void CefPrintViewManagerBase::Observe(
|
|
int type,
|
|
const content::NotificationSource& source,
|
|
const content::NotificationDetails& details) {
|
|
DCHECK_EQ(chrome::NOTIFICATION_PRINT_JOB_EVENT, type);
|
|
OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
|
|
}
|
|
|
|
void CefPrintViewManagerBase::OnNotifyPrintJobEvent(
|
|
const JobEventDetails& event_details) {
|
|
switch (event_details.type()) {
|
|
case JobEventDetails::FAILED: {
|
|
TerminatePrintJob(true);
|
|
|
|
content::NotificationService::current()->Notify(
|
|
chrome::NOTIFICATION_PRINT_JOB_RELEASED,
|
|
content::Source<content::WebContents>(web_contents()),
|
|
content::NotificationService::NoDetails());
|
|
break;
|
|
}
|
|
case JobEventDetails::USER_INIT_DONE:
|
|
case JobEventDetails::DEFAULT_INIT_DONE:
|
|
case JobEventDetails::USER_INIT_CANCELED: {
|
|
NOTREACHED();
|
|
break;
|
|
}
|
|
case JobEventDetails::ALL_PAGES_REQUESTED: {
|
|
ShouldQuitFromInnerMessageLoop();
|
|
break;
|
|
}
|
|
case JobEventDetails::NEW_DOC:
|
|
#if defined(OS_WIN)
|
|
case JobEventDetails::PAGE_DONE:
|
|
#endif
|
|
case JobEventDetails::DOC_DONE: {
|
|
// Don't care about the actual printing process.
|
|
break;
|
|
}
|
|
case JobEventDetails::JOB_DONE: {
|
|
// Printing is done, we don't need it anymore.
|
|
// print_job_->is_job_pending() may still be true, depending on the order
|
|
// of object registration.
|
|
printing_succeeded_ = true;
|
|
ReleasePrintJob();
|
|
|
|
content::NotificationService::current()->Notify(
|
|
chrome::NOTIFICATION_PRINT_JOB_RELEASED,
|
|
content::Source<content::WebContents>(web_contents()),
|
|
content::NotificationService::NoDetails());
|
|
break;
|
|
}
|
|
default: {
|
|
NOTREACHED();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::RenderAllMissingPagesNow() {
|
|
if (!print_job_ || !print_job_->is_job_pending())
|
|
return false;
|
|
|
|
// Is the document already complete?
|
|
if (print_job_->document() && print_job_->document()->IsComplete()) {
|
|
printing_succeeded_ = true;
|
|
return true;
|
|
}
|
|
|
|
// We can't print if there is no renderer.
|
|
if (!web_contents() || !web_contents()->GetRenderViewHost() ||
|
|
!web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
|
|
return false;
|
|
}
|
|
|
|
// WebContents is either dying or a second consecutive request to print
|
|
// happened before the first had time to finish. We need to render all the
|
|
// pages in an hurry if a print_job_ is still pending. No need to wait for it
|
|
// to actually spool the pages, only to have the renderer generate them. Run
|
|
// a message loop until we get our signal that the print job is satisfied.
|
|
// PrintJob will send a ALL_PAGES_REQUESTED after having received all the
|
|
// pages it needs. RunLoop::QuitCurrentWhenIdleDeprecated() will be called as
|
|
// soon as print_job_->document()->IsComplete() is true on either
|
|
// ALL_PAGES_REQUESTED or in DidPrintDocument(). The check is done in
|
|
// ShouldQuitFromInnerMessageLoop().
|
|
// BLOCKS until all the pages are received. (Need to enable recursive task)
|
|
if (!RunInnerMessageLoop()) {
|
|
// This function is always called from DisconnectFromCurrentPrintJob() so we
|
|
// know that the job will be stopped/canceled in any case.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CefPrintViewManagerBase::ShouldQuitFromInnerMessageLoop() {
|
|
// Look at the reason.
|
|
DCHECK(print_job_->document());
|
|
if (print_job_->document() && print_job_->document()->IsComplete() &&
|
|
inside_inner_message_loop_) {
|
|
// We are in a message loop created by RenderAllMissingPagesNow. Quit from
|
|
// it.
|
|
base::RunLoop::QuitCurrentWhenIdleDeprecated();
|
|
inside_inner_message_loop_ = false;
|
|
}
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::CreateNewPrintJob(PrinterQuery* query) {
|
|
DCHECK(!inside_inner_message_loop_);
|
|
DCHECK(query);
|
|
|
|
// Disconnect the current |print_job_|.
|
|
DisconnectFromCurrentPrintJob();
|
|
|
|
// We can't print if there is no renderer.
|
|
if (!web_contents()->GetRenderViewHost() ||
|
|
!web_contents()->GetRenderViewHost()->IsRenderViewLive()) {
|
|
return false;
|
|
}
|
|
|
|
DCHECK(!print_job_);
|
|
print_job_ = base::MakeRefCounted<PrintJob>();
|
|
print_job_->Initialize(query, RenderSourceName(), number_pages_);
|
|
registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
|
|
content::Source<PrintJob>(print_job_.get()));
|
|
printing_succeeded_ = false;
|
|
return true;
|
|
}
|
|
|
|
void CefPrintViewManagerBase::DisconnectFromCurrentPrintJob() {
|
|
// Make sure all the necessary rendered page are done. Don't bother with the
|
|
// return value.
|
|
bool result = RenderAllMissingPagesNow();
|
|
|
|
// Verify that assertion.
|
|
if (print_job_ && print_job_->document() &&
|
|
!print_job_->document()->IsComplete()) {
|
|
DCHECK(!result);
|
|
// That failed.
|
|
TerminatePrintJob(true);
|
|
} else {
|
|
// DO NOT wait for the job to finish.
|
|
ReleasePrintJob();
|
|
}
|
|
}
|
|
|
|
void CefPrintViewManagerBase::TerminatePrintJob(bool cancel) {
|
|
if (!print_job_)
|
|
return;
|
|
|
|
if (cancel) {
|
|
// We don't need the metafile data anymore because the printing is canceled.
|
|
print_job_->Cancel();
|
|
inside_inner_message_loop_ = false;
|
|
} else {
|
|
DCHECK(!inside_inner_message_loop_);
|
|
DCHECK(!print_job_->document() || print_job_->document()->IsComplete());
|
|
|
|
// WebContents is either dying or navigating elsewhere. We need to render
|
|
// all the pages in an hurry if a print job is still pending. This does the
|
|
// trick since it runs a blocking message loop:
|
|
print_job_->Stop();
|
|
}
|
|
ReleasePrintJob();
|
|
}
|
|
|
|
void CefPrintViewManagerBase::ReleasePrintJob() {
|
|
content::RenderFrameHost* rfh = printing_rfh_;
|
|
printing_rfh_ = nullptr;
|
|
|
|
if (!print_job_)
|
|
return;
|
|
|
|
if (rfh) {
|
|
auto msg = std::make_unique<PrintMsg_PrintingDone>(rfh->GetRoutingID(),
|
|
printing_succeeded_);
|
|
rfh->Send(msg.release());
|
|
}
|
|
|
|
registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
|
|
content::Source<PrintJob>(print_job_.get()));
|
|
// Don't close the worker thread.
|
|
print_job_ = nullptr;
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::RunInnerMessageLoop() {
|
|
// This value may actually be too low:
|
|
//
|
|
// - If we're looping because of printer settings initialization, the premise
|
|
// here is that some poor users have their print server away on a VPN over a
|
|
// slow connection. In this situation, the simple fact of opening the printer
|
|
// can be dead slow. On the other side, we don't want to die infinitely for a
|
|
// real network error. Give the printer 60 seconds to comply.
|
|
//
|
|
// - If we're looping because of renderer page generation, the renderer could
|
|
// be CPU bound, the page overly complex/large or the system just
|
|
// memory-bound.
|
|
static const int kPrinterSettingsTimeout = 60000;
|
|
base::OneShotTimer quit_timer;
|
|
base::RunLoop run_loop;
|
|
quit_timer.Start(FROM_HERE,
|
|
TimeDelta::FromMilliseconds(kPrinterSettingsTimeout),
|
|
run_loop.QuitWhenIdleClosure());
|
|
|
|
inside_inner_message_loop_ = true;
|
|
|
|
// Need to enable recursive task.
|
|
{
|
|
base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
|
|
run_loop.Run();
|
|
}
|
|
|
|
bool success = true;
|
|
if (inside_inner_message_loop_) {
|
|
// Ok we timed out. That's sad.
|
|
inside_inner_message_loop_ = false;
|
|
success = false;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) {
|
|
if (print_job_)
|
|
return true;
|
|
|
|
if (!cookie) {
|
|
// Out of sync. It may happens since we are completely asynchronous. Old
|
|
// spurious message can happen if one of the processes is overloaded.
|
|
return false;
|
|
}
|
|
|
|
// The job was initiated by a script. Time to get the corresponding worker
|
|
// thread.
|
|
scoped_refptr<PrinterQuery> queued_query = queue_->PopPrinterQuery(cookie);
|
|
if (!queued_query) {
|
|
NOTREACHED();
|
|
return false;
|
|
}
|
|
|
|
if (!CreateNewPrintJob(queued_query.get())) {
|
|
// Don't kill anything.
|
|
return false;
|
|
}
|
|
|
|
// Settings are already loaded. Go ahead. This will set
|
|
// print_job_->is_job_pending() to true.
|
|
print_job_->StartPrinting();
|
|
return true;
|
|
}
|
|
|
|
bool CefPrintViewManagerBase::PrintNowInternal(
|
|
content::RenderFrameHost* rfh,
|
|
std::unique_ptr<IPC::Message> message) {
|
|
// Don't print / print preview interstitials or crashed tabs.
|
|
if (web_contents()->ShowingInterstitialPage() || web_contents()->IsCrashed())
|
|
return false;
|
|
return rfh->Send(message.release());
|
|
}
|
|
|
|
void CefPrintViewManagerBase::SetPrintingRFH(content::RenderFrameHost* rfh) {
|
|
DCHECK(!printing_rfh_);
|
|
printing_rfh_ = rfh;
|
|
}
|
|
|
|
void CefPrintViewManagerBase::ReleasePrinterQuery() {
|
|
if (!cookie_)
|
|
return;
|
|
|
|
int cookie = cookie_;
|
|
cookie_ = 0;
|
|
|
|
PrintJobManager* print_job_manager = g_browser_process->print_job_manager();
|
|
// May be NULL in tests.
|
|
if (!print_job_manager)
|
|
return;
|
|
|
|
scoped_refptr<PrinterQuery> printer_query;
|
|
printer_query = queue_->PopPrinterQuery(cookie);
|
|
if (!printer_query)
|
|
return;
|
|
BrowserThread::PostTask(
|
|
BrowserThread::IO, FROM_HERE,
|
|
base::BindOnce(&PrinterQuery::StopWorker, printer_query));
|
|
}
|
|
|
|
void CefPrintViewManagerBase::SendPrintingEnabled(
|
|
bool enabled,
|
|
content::RenderFrameHost* rfh) {
|
|
rfh->Send(new PrintMsg_SetPrintingEnabled(rfh->GetRoutingID(), enabled));
|
|
}
|
|
|
|
} // namespace printing
|