Chromium Code Reviews| Index: chrome/service/service_utility_process_host.cc |
| diff --git a/chrome/service/service_utility_process_host.cc b/chrome/service/service_utility_process_host.cc |
| index df2cf9400d0302903a1d352a863a383c99ccdc0d..c97aaa54be04bb495dbe2eb9943092468604a993 100644 |
| --- a/chrome/service/service_utility_process_host.cc |
| +++ b/chrome/service/service_utility_process_host.cc |
| @@ -6,49 +6,53 @@ |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| +#include "base/files/file.h" |
| +#include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| -#include "base/files/scoped_temp_dir.h" |
| #include "base/logging.h" |
| -#include "base/message_loop/message_loop.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/metrics/histogram.h" |
| #include "base/process/kill.h" |
| -#include "base/strings/utf_string_conversions.h" |
| +#include "base/process/launch.h" |
| +#include "base/task_runner_util.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/chrome_utility_printing_messages.h" |
| #include "content/public/common/child_process_host.h" |
| #include "content/public/common/result_codes.h" |
| #include "content/public/common/sandbox_init.h" |
| -#include "ipc/ipc_switches.h" |
| -#include "printing/page_range.h" |
| -#include "ui/base/ui_base_switches.h" |
| -#include "ui/gfx/rect.h" |
| - |
| -#if defined(OS_WIN) |
| -#include "base/files/file_path.h" |
| -#include "base/memory/scoped_ptr.h" |
| -#include "base/process/launch.h" |
| -#include "base/win/scoped_handle.h" |
| -#include "content/public/common/sandbox_init.h" |
| #include "content/public/common/sandboxed_process_launcher_delegate.h" |
| +#include "ipc/ipc_switches.h" |
| #include "printing/emf_win.h" |
| #include "sandbox/win/src/sandbox_policy_base.h" |
| +#include "ui/base/ui_base_switches.h" |
| namespace { |
| +using content::ChildProcessHost; |
| + |
| +const int kMaxNumberOfTempFilesPerDocument = 3; |
| + |
| +enum ServiceUtilityProcessHostEvent { |
| + SERVICE_UTILITY_STARTED, |
| + SERVICE_UTILITY_DISCONNECTED, |
| + SERVICE_UTILITY_METAFILE_REQUEST, |
| + SERVICE_UTILITY_METAFILE_SUCCEEDED, |
| + SERVICE_UTILITY_METAFILE_FAILED, |
| + SERVICE_UTILITY_CAPS_REQUEST, |
| + SERVICE_UTILITY_CAPS_SUCCEEDED, |
| + SERVICE_UTILITY_CAPS_FAILED, |
| + SERVICE_UTILITY_SEMANTIC_CAPS_REQUEST, |
| + SERVICE_UTILITY_SEMANTIC_CAPS_SUCCEEDED, |
| + SERVICE_UTILITY_SEMANTIC_CAPS_FAILED, |
| + SERVICE_UTILITY_FAILED_TO_START, |
| + SERVICE_UTILITY_EVENT_MAX, |
| +}; |
| + |
| // NOTE: changes to this class need to be reviewed by the security team. |
| class ServiceSandboxedProcessLauncherDelegate |
| : public content::SandboxedProcessLauncherDelegate { |
| public: |
| - explicit ServiceSandboxedProcessLauncherDelegate( |
| - const base::FilePath& exposed_dir) |
| - : exposed_dir_(exposed_dir) { |
| - } |
| - |
| - virtual void PreSandbox(bool* disable_default_policy, |
| - base::FilePath* exposed_dir) OVERRIDE { |
| - *exposed_dir = exposed_dir_; |
| - } |
| + ServiceSandboxedProcessLauncherDelegate() {} |
| virtual void PreSpawnTarget(sandbox::TargetPolicy* policy, |
| bool* success) OVERRIDE { |
| @@ -58,38 +62,85 @@ class ServiceSandboxedProcessLauncherDelegate |
| } |
| private: |
|
Lei Zhang
2014/09/16 03:36:55
private block looks empty, DISALLOW_COPY_AND_ASSIG
Vitaly Buka (NO REVIEWS)
2014/09/16 07:50:36
Done.
|
| - base::FilePath exposed_dir_; |
| }; |
| +base::File CreateTempFile() { |
| + base::FilePath path; |
| + if (!base::CreateTemporaryFile(&path)) |
| + return base::File(); |
| + return base::File(path, |
| + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | |
| + base::File::FLAG_READ | |
| + base::File::FLAG_DELETE_ON_CLOSE | |
| + base::File::FLAG_TEMPORARY); |
| +} |
| + |
| } // namespace |
| -#endif // OS_WIN |
| +class ServiceUtilityProcessHost::PdfToEmfState { |
| + public: |
| + explicit PdfToEmfState(ServiceUtilityProcessHost* host) |
| + : host_(host), page_count_(0), current_page_(0), pages_in_progress_(0) {} |
| + ~PdfToEmfState() { Stop(); } |
| + |
| + bool Start(base::File pdf_file, |
| + const printing::PdfRenderSettings& conversion_settings) { |
| + return host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles( |
| + IPC::TakeFileHandleForProcess(pdf_file.Pass(), host_->handle()), |
| + conversion_settings)); |
| + } |
| -using content::ChildProcessHost; |
| + void GetMorePages() { |
| + while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument && |
| + current_page_ < page_count_) { |
| + ++pages_in_progress_; |
| + emf_files_.push_back(CreateTempFile()); |
|
Lei Zhang
2014/09/16 03:36:55
Do you care if CreateTempFile() fails?
Vitaly Buka (NO REVIEWS)
2014/09/16 07:50:36
I don't. Invalid handle will be passed to utility
Lei Zhang
2014/09/16 19:57:36
I don't think CreateTemporaryFile() will have coll
Vitaly Buka (NO REVIEWS)
2014/09/16 22:10:37
If some process, not even Chrome leak files after
|
| + host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage( |
| + current_page_++, |
| + IPC::GetFileHandleForProcess( |
| + emf_files_.back().GetPlatformFile(), host_->handle(), false))); |
| + } |
| + } |
| -namespace { |
| -enum ServiceUtilityProcessHostEvent { |
| - SERVICE_UTILITY_STARTED, |
| - SERVICE_UTILITY_DISCONNECTED, |
| - SERVICE_UTILITY_METAFILE_REQUEST, |
| - SERVICE_UTILITY_METAFILE_SUCCEEDED, |
| - SERVICE_UTILITY_METAFILE_FAILED, |
| - SERVICE_UTILITY_CAPS_REQUEST, |
| - SERVICE_UTILITY_CAPS_SUCCEEDED, |
| - SERVICE_UTILITY_CAPS_FAILED, |
| - SERVICE_UTILITY_SEMANTIC_CAPS_REQUEST, |
| - SERVICE_UTILITY_SEMANTIC_CAPS_SUCCEEDED, |
| - SERVICE_UTILITY_SEMANTIC_CAPS_FAILED, |
| - SERVICE_UTILITY_EVENT_MAX, |
| + bool OnPageProcessed() { |
|
Lei Zhang
2014/09/16 03:36:54
document return value
Vitaly Buka (NO REVIEWS)
2014/09/16 07:50:36
Done.
|
| + --pages_in_progress_; |
| + GetMorePages(); |
| + if (pages_in_progress_ || current_page_ < page_count_) |
| + return false; |
| + Stop(); |
| + return true; |
| + } |
| + |
| + base::File TakeNextFile() { |
| + DCHECK(!emf_files_.empty()); |
| + base::File file; |
| + if (!emf_files_.empty()) |
| + file = emf_files_.front().Pass(); |
| + emf_files_.pop_front(); |
| + return file.Pass(); |
| + } |
| + |
| + void set_page_count(int page_count) { page_count_ = page_count; } |
| + |
| + private: |
| + void Stop() { |
| + host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop()); |
| + } |
| + ServiceUtilityProcessHost* host_; |
| + std::deque<base::File> emf_files_; |
| + int page_count_; |
| + int current_page_; |
| + int pages_in_progress_; |
| }; |
| -} // namespace |
| ServiceUtilityProcessHost::ServiceUtilityProcessHost( |
| - Client* client, base::MessageLoopProxy* client_message_loop_proxy) |
| - : handle_(base::kNullProcessHandle), |
| - client_(client), |
| - client_message_loop_proxy_(client_message_loop_proxy), |
| - waiting_for_reply_(false) { |
| + Client* client, |
| + base::MessageLoopProxy* client_message_loop_proxy) |
| + : handle_(base::kNullProcessHandle), |
| + client_(client), |
| + client_message_loop_proxy_(client_message_loop_proxy), |
| + waiting_for_reply_(false), |
| + weak_ptr_factory_(this) { |
| child_process_host_.reset(ChildProcessHost::Create(this)); |
| } |
| @@ -100,37 +151,20 @@ ServiceUtilityProcessHost::~ServiceUtilityProcessHost() { |
| bool ServiceUtilityProcessHost::StartRenderPDFPagesToMetafile( |
| const base::FilePath& pdf_path, |
| - const printing::PdfRenderSettings& render_settings, |
| - const std::vector<printing::PageRange>& page_ranges) { |
| + const printing::PdfRenderSettings& render_settings) { |
| UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", |
| SERVICE_UTILITY_METAFILE_REQUEST, |
| SERVICE_UTILITY_EVENT_MAX); |
| start_time_ = base::Time::Now(); |
| -#if !defined(OS_WIN) |
| - // This is only implemented on Windows (because currently it is only needed |
| - // on Windows). Will add implementations on other platforms when needed. |
| - NOTIMPLEMENTED(); |
| - return false; |
| -#else // !defined(OS_WIN) |
| - scratch_metafile_dir_.reset(new base::ScopedTempDir); |
| - if (!scratch_metafile_dir_->CreateUniqueTempDir()) |
| - return false; |
| - metafile_path_ = scratch_metafile_dir_->path().AppendASCII("output.emf"); |
| - if (!StartProcess(false, scratch_metafile_dir_->path())) |
| + base::File pdf_file(pdf_path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| + if (!pdf_file.IsValid() || !StartProcess(false)) |
| return false; |
| - base::File pdf_file( |
| - pdf_path, |
| - base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE); |
| DCHECK(!waiting_for_reply_); |
| waiting_for_reply_ = true; |
| - return child_process_host_->Send( |
| - new ChromeUtilityMsg_RenderPDFPagesToMetafiles( |
| - IPC::TakeFileHandleForProcess(pdf_file.Pass(), handle()), |
| - metafile_path_, |
| - render_settings, |
| - page_ranges)); |
| -#endif // !defined(OS_WIN) |
| + |
| + pdf_to_emf_state_.reset(new PdfToEmfState(this)); |
| + return pdf_to_emf_state_->Start(pdf_file.Pass(), render_settings); |
| } |
| bool ServiceUtilityProcessHost::StartGetPrinterCapsAndDefaults( |
| @@ -139,13 +173,11 @@ bool ServiceUtilityProcessHost::StartGetPrinterCapsAndDefaults( |
| SERVICE_UTILITY_CAPS_REQUEST, |
| SERVICE_UTILITY_EVENT_MAX); |
| start_time_ = base::Time::Now(); |
| - base::FilePath exposed_path; |
| - if (!StartProcess(true, exposed_path)) |
| + if (!StartProcess(true)) |
| return false; |
| DCHECK(!waiting_for_reply_); |
| waiting_for_reply_ = true; |
| - return child_process_host_->Send( |
| - new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name)); |
| + return Send(new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name)); |
| } |
| bool ServiceUtilityProcessHost::StartGetPrinterSemanticCapsAndDefaults( |
| @@ -154,18 +186,22 @@ bool ServiceUtilityProcessHost::StartGetPrinterSemanticCapsAndDefaults( |
| SERVICE_UTILITY_SEMANTIC_CAPS_REQUEST, |
| SERVICE_UTILITY_EVENT_MAX); |
| start_time_ = base::Time::Now(); |
| - base::FilePath exposed_path; |
| - if (!StartProcess(true, exposed_path)) |
| + if (!StartProcess(true)) |
| return false; |
| DCHECK(!waiting_for_reply_); |
| waiting_for_reply_ = true; |
| - return child_process_host_->Send( |
| + return Send( |
| new ChromeUtilityMsg_GetPrinterSemanticCapsAndDefaults(printer_name)); |
| } |
| -bool ServiceUtilityProcessHost::StartProcess( |
| - bool no_sandbox, |
| - const base::FilePath& exposed_dir) { |
| +bool ServiceUtilityProcessHost::Send(IPC::Message* msg) { |
| + if (child_process_host_) |
| + return child_process_host_->Send(msg); |
| + delete msg; |
| + return false; |
| +} |
| + |
| +bool ServiceUtilityProcessHost::StartProcess(bool no_sandbox) { |
| std::string channel_id = child_process_host_->CreateChannel(); |
| if (channel_id.empty()) |
| return false; |
| @@ -181,34 +217,28 @@ bool ServiceUtilityProcessHost::StartProcess( |
| cmd_line.AppendSwitchASCII(switches::kProcessChannelID, channel_id); |
| cmd_line.AppendSwitch(switches::kLang); |
| - if (Launch(&cmd_line, no_sandbox, exposed_dir)) { |
| + if (Launch(&cmd_line, no_sandbox)) { |
| UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", |
| SERVICE_UTILITY_STARTED, |
| SERVICE_UTILITY_EVENT_MAX); |
| return true; |
| } |
| + UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", |
| + SERVICE_UTILITY_FAILED_TO_START, |
| + SERVICE_UTILITY_EVENT_MAX); |
| return false; |
| } |
| -bool ServiceUtilityProcessHost::Launch(CommandLine* cmd_line, |
| - bool no_sandbox, |
| - const base::FilePath& exposed_dir) { |
| -#if !defined(OS_WIN) |
| - // TODO(sanjeevr): Implement for non-Windows OSes. |
| - NOTIMPLEMENTED(); |
| - return false; |
| -#else // !defined(OS_WIN) |
| - |
| +bool ServiceUtilityProcessHost::Launch(CommandLine* cmd_line, bool no_sandbox) { |
|
Lei Zhang
2014/09/16 03:36:55
nit: base::CommandLine
Vitaly Buka (NO REVIEWS)
2014/09/16 07:50:36
Done.
|
| if (no_sandbox) { |
| base::ProcessHandle process = base::kNullProcessHandle; |
| cmd_line->AppendSwitch(switches::kNoSandbox); |
| base::LaunchProcess(*cmd_line, base::LaunchOptions(), &handle_); |
| } else { |
| - ServiceSandboxedProcessLauncherDelegate delegate(exposed_dir); |
| + ServiceSandboxedProcessLauncherDelegate delegate; |
| handle_ = content::StartSandboxedProcess(&delegate, cmd_line); |
| } |
| return (handle_ != base::kNullProcessHandle); |
| -#endif // !defined(OS_WIN) |
| } |
| base::FilePath ServiceUtilityProcessHost::GetUtilityProcessCmd() { |
| @@ -238,13 +268,11 @@ void ServiceUtilityProcessHost::OnChildDisconnected() { |
| bool ServiceUtilityProcessHost::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost, message) |
| -#if defined(OS_WIN) |
| IPC_MESSAGE_HANDLER( |
| - ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_Succeeded, |
| - OnRenderPDFPagesToMetafilesSucceeded) |
| - IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Failed, |
| - OnRenderPDFPagesToMetafileFailed) |
| -#endif |
| + ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount, |
| + OnRenderPDFPagesToMetafilesPageCount) |
| + IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone, |
| + OnRenderPDFPagesToMetafilesPageDone) |
| IPC_MESSAGE_HANDLER( |
| ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded, |
| OnGetPrinterCapsAndDefaultsSucceeded) |
| @@ -265,51 +293,61 @@ base::ProcessHandle ServiceUtilityProcessHost::GetHandle() const { |
| return handle_; |
| } |
| -#if defined(OS_WIN) |
| -void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesSucceeded( |
| - const std::vector<printing::PageRange>& page_ranges, |
| +void ServiceUtilityProcessHost::OnMetafileSpooled(bool success) { |
| + if (!success || pdf_to_emf_state_->OnPageProcessed()) |
| + OnPDFToEmfFinished(success); |
| +} |
| + |
| +void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageCount( |
| + int page_count) { |
| + DCHECK(waiting_for_reply_); |
|
Lei Zhang
2014/09/16 03:36:55
Add "bool PdfToEmfState::has_page_count()" { retur
Vitaly Buka (NO REVIEWS)
2014/09/16 07:50:36
Done.
|
| + if (!pdf_to_emf_state_ || page_count <= 0) |
| + return OnPDFToEmfFinished(false); |
| + pdf_to_emf_state_->set_page_count(page_count); |
| + pdf_to_emf_state_->GetMorePages(); |
| +} |
| + |
| +void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageDone( |
| + bool success, |
| double scale_factor) { |
| - UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", |
| - SERVICE_UTILITY_METAFILE_SUCCEEDED, |
| - SERVICE_UTILITY_EVENT_MAX); |
| - UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileTime", |
| - base::Time::Now() - start_time_); |
| DCHECK(waiting_for_reply_); |
| - waiting_for_reply_ = false; |
| - // If the metafile was successfully created, we need to take our hands off the |
| - // scratch metafile directory. The client will delete it when it is done with |
| - // metafile. |
| - scratch_metafile_dir_->Take(); |
| - |
| - // TODO(vitalybuka|scottmg): http://crbug.com/170859: Currently, only one |
| - // page is printed at a time. This would need to be refactored to change |
| - // this. |
| - CHECK_EQ(1u, page_ranges.size()); |
| - CHECK_EQ(page_ranges[0].from, page_ranges[0].to); |
| - int page_number = page_ranges[0].from; |
| - client_message_loop_proxy_->PostTask( |
| + if (!pdf_to_emf_state_ || !success) |
| + return OnPDFToEmfFinished(false); |
| + base::File emf_file = pdf_to_emf_state_->TakeNextFile(); |
| + base::PostTaskAndReplyWithResult( |
| + client_message_loop_proxy_, |
| FROM_HERE, |
| base::Bind(&Client::MetafileAvailable, |
| client_.get(), |
| - metafile_path_.InsertBeforeExtensionASCII( |
| - base::StringPrintf(".%d", page_number)), |
| - page_number, |
| - scale_factor)); |
| + scale_factor, |
| + base::Passed(&emf_file)), |
| + base::Bind(&ServiceUtilityProcessHost::OnMetafileSpooled, |
| + weak_ptr_factory_.GetWeakPtr())); |
| } |
| -void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileFailed() { |
| - DCHECK(waiting_for_reply_); |
| - UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", |
| - SERVICE_UTILITY_METAFILE_FAILED, |
| - SERVICE_UTILITY_EVENT_MAX); |
| - UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileFailTime", |
| - base::Time::Now() - start_time_); |
| +void ServiceUtilityProcessHost::OnPDFToEmfFinished(bool success) { |
| + if (!waiting_for_reply_) |
| + return; |
| waiting_for_reply_ = false; |
| + if (success) { |
| + UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", |
| + SERVICE_UTILITY_METAFILE_SUCCEEDED, |
| + SERVICE_UTILITY_EVENT_MAX); |
| + UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileTime", |
| + base::Time::Now() - start_time_); |
| + } else { |
| + UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", |
| + SERVICE_UTILITY_METAFILE_FAILED, |
| + SERVICE_UTILITY_EVENT_MAX); |
| + UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileFailTime", |
| + base::Time::Now() - start_time_); |
| + } |
| client_message_loop_proxy_->PostTask( |
| FROM_HERE, |
| - base::Bind(&Client::OnRenderPDFPagesToMetafileFailed, client_.get())); |
| + base::Bind( |
| + &Client::OnRenderPDFPagesToMetafileDone, client_.get(), success)); |
| + pdf_to_emf_state_.reset(); |
| } |
| -#endif // defined(OS_WIN) |
| void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsSucceeded( |
| const std::string& printer_name, |
| @@ -374,27 +412,24 @@ void ServiceUtilityProcessHost::OnGetPrinterSemanticCapsAndDefaultsFailed( |
| printing::PrinterSemanticCapsAndDefaults())); |
| } |
| -void ServiceUtilityProcessHost::Client::MetafileAvailable( |
| - const base::FilePath& metafile_path, |
| - int highest_rendered_page_number, |
| - double scale_factor) { |
| - // The metafile was created in a temp folder which needs to get deleted after |
| - // we have processed it. |
| - base::ScopedTempDir scratch_metafile_dir; |
| - if (!scratch_metafile_dir.Set(metafile_path.DirName())) |
| - LOG(WARNING) << "Unable to set scratch metafile directory"; |
| -#if defined(OS_WIN) |
| - // It's important that metafile is declared after scratch_metafile_dir so |
| - // that the metafile destructor closes the file before the base::ScopedTempDir |
| - // destructor tries to remove the directory. |
| - printing::Emf metafile; |
| - if (!metafile.InitFromFile(metafile_path)) { |
| - OnRenderPDFPagesToMetafileFailed(); |
| - } else { |
| - OnRenderPDFPagesToMetafileSucceeded(metafile, |
| - highest_rendered_page_number, |
| - scale_factor); |
| +bool ServiceUtilityProcessHost::Client::MetafileAvailable(double scale_factor, |
| + base::File file) { |
| + file.Seek(base::File::FROM_BEGIN, 0); |
| + int64 size = file.GetLength(); |
| + if (size <= 0) { |
| + OnRenderPDFPagesToMetafileDone(false); |
| + return false; |
| + } |
| + std::vector<char> data(size); |
| + if (file.ReadAtCurrentPos(&data[0], data.size()) != size) { |
|
Lei Zhang
2014/09/16 03:36:55
nit: also data.data()
Vitaly Buka (NO REVIEWS)
2014/09/16 07:50:36
Done.
|
| + OnRenderPDFPagesToMetafileDone(false); |
| + return false; |
| } |
| -#endif // defined(OS_WIN) |
| + printing::Emf emf; |
| + if (!emf.InitFromData(&data[0], data.size())) { |
| + OnRenderPDFPagesToMetafileDone(false); |
| + return false; |
| + } |
| + OnRenderPDFPagesToMetafilePageDone(scale_factor, emf); |
| + return true; |
| } |
| - |