1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/printing/pdf_to_emf_converter.h"
9 #include "base/files/file.h"
10 #include "base/files/file_util.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/logging.h"
13 #include "chrome/common/chrome_utility_messages.h"
14 #include "chrome/common/chrome_utility_printing_messages.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/child_process_data.h"
18 #include "content/public/browser/utility_process_host.h"
19 #include "content/public/browser/utility_process_host_client.h"
20 #include "printing/emf_win.h"
21 #include "printing/pdf_render_settings.h"
22 #include "ui/base/l10n/l10n_util.h"
28 using content::BrowserThread
;
30 class PdfToEmfConverterImpl
;
32 // Allows to delete temporary directory after all temporary files created inside
33 // are closed. Windows cannot delete directory with opened files. Directory is
34 // used to store PDF and metafiles. PDF should be gone by the time utility
35 // process exits. Metafiles should be gone when all LazyEmf destroyed.
36 class RefCountedTempDir
37 : public base::RefCountedThreadSafe
<RefCountedTempDir
,
38 BrowserThread::DeleteOnFileThread
> {
40 RefCountedTempDir() { ignore_result(temp_dir_
.CreateUniqueTempDir()); }
41 bool IsValid() const { return temp_dir_
.IsValid(); }
42 const base::FilePath
& GetPath() const { return temp_dir_
.path(); }
45 friend struct BrowserThread::DeleteOnThread
<BrowserThread::FILE>;
46 friend class base::DeleteHelper
<RefCountedTempDir
>;
47 ~RefCountedTempDir() {}
49 base::ScopedTempDir temp_dir_
;
50 DISALLOW_COPY_AND_ASSIGN(RefCountedTempDir
);
53 typedef scoped_ptr
<base::File
, BrowserThread::DeleteOnFileThread
>
56 // Wrapper for Emf to keep only file handle in memory, and load actual data only
57 // on playback. Emf::InitFromFile() can play metafile directly from disk, but it
58 // can't open file handles. We need file handles to reliably delete temporary
59 // files, and to efficiently interact with utility process.
60 class LazyEmf
: public MetafilePlayer
{
62 LazyEmf(const scoped_refptr
<RefCountedTempDir
>& temp_dir
, ScopedTempFile file
)
63 : temp_dir_(temp_dir
), file_(file
.Pass()) {
66 ~LazyEmf() override
{ Close(); }
68 bool SafePlayback(HDC hdc
) const override
;
69 bool SaveTo(base::File
* file
) const override
;
73 bool LoadEmf(Emf
* emf
) const;
75 mutable scoped_refptr
<RefCountedTempDir
> temp_dir_
;
76 mutable ScopedTempFile file_
; // Mutable because of consts in base class.
78 DISALLOW_COPY_AND_ASSIGN(LazyEmf
);
81 // Converts PDF into EMF.
82 // Class uses 3 threads: UI, IO and FILE.
83 // Internal workflow is following:
84 // 1. Create instance on the UI thread. (files_, settings_,)
85 // 2. Create pdf file on the FILE thread.
86 // 3. Start utility process and start conversion on the IO thread.
87 // 4. Utility process returns page count.
89 // 1. Clients requests page with file handle to a temp file.
90 // 2. Utility converts the page, save it to the file and reply.
92 // All these steps work sequentially, so no data should be accessed
93 // simultaneously by several threads.
94 class PdfToEmfUtilityProcessHostClient
95 : public content::UtilityProcessHostClient
{
97 PdfToEmfUtilityProcessHostClient(
98 base::WeakPtr
<PdfToEmfConverterImpl
> converter
,
99 const PdfRenderSettings
& settings
);
101 void Start(const scoped_refptr
<base::RefCountedMemory
>& data
,
102 const PdfToEmfConverter::StartCallback
& start_callback
);
104 void GetPage(int page_number
,
105 const PdfToEmfConverter::GetPageCallback
& get_page_callback
);
109 // UtilityProcessHostClient implementation.
110 void OnProcessCrashed(int exit_code
) override
;
111 void OnProcessLaunchFailed() override
;
112 bool OnMessageReceived(const IPC::Message
& message
) override
;
115 class GetPageCallbackData
{
116 MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData
, RValue
);
119 GetPageCallbackData(int page_number
,
120 PdfToEmfConverter::GetPageCallback callback
)
121 : page_number_(page_number
), callback_(callback
) {}
123 // Move constructor for STL.
124 GetPageCallbackData(RValue other
) { this->operator=(other
); }
126 // Move assignment for STL.
127 GetPageCallbackData
& operator=(RValue rhs
) {
128 page_number_
= rhs
.object
->page_number_
;
129 callback_
= rhs
.object
->callback_
;
130 emf_
= rhs
.object
->emf_
.Pass();
134 int page_number() const { return page_number_
; }
135 const PdfToEmfConverter::GetPageCallback
& callback() const {
138 ScopedTempFile
TakeEmf() { return emf_
.Pass(); }
139 void set_emf(ScopedTempFile emf
) { emf_
= emf
.Pass(); }
143 PdfToEmfConverter::GetPageCallback callback_
;
147 ~PdfToEmfUtilityProcessHostClient() override
;
149 bool Send(IPC::Message
* msg
);
152 void OnProcessStarted();
153 void OnPageCount(int page_count
);
154 void OnPageDone(bool success
, float scale_factor
);
157 void OnTempPdfReady(ScopedTempFile pdf
);
158 void OnTempEmfReady(GetPageCallbackData
* callback_data
, ScopedTempFile emf
);
160 scoped_refptr
<RefCountedTempDir
> temp_dir_
;
162 // Used to suppress callbacks after PdfToEmfConverterImpl is deleted.
163 base::WeakPtr
<PdfToEmfConverterImpl
> converter_
;
164 PdfRenderSettings settings_
;
165 scoped_refptr
<base::RefCountedMemory
> data_
;
167 // Document loaded callback.
168 PdfToEmfConverter::StartCallback start_callback_
;
170 // Process host for IPC.
171 base::WeakPtr
<content::UtilityProcessHost
> utility_process_host_
;
173 // Queue of callbacks for GetPage() requests. Utility process should reply
174 // with PageDone in the same order as requests were received.
175 // Use containers that keeps element pointers valid after push() and pop().
176 typedef std::queue
<GetPageCallbackData
> GetPageCallbacks
;
177 GetPageCallbacks get_page_callbacks_
;
179 DISALLOW_COPY_AND_ASSIGN(PdfToEmfUtilityProcessHostClient
);
182 class PdfToEmfConverterImpl
: public PdfToEmfConverter
{
184 PdfToEmfConverterImpl();
186 ~PdfToEmfConverterImpl() override
;
188 void Start(const scoped_refptr
<base::RefCountedMemory
>& data
,
189 const PdfRenderSettings
& conversion_settings
,
190 const StartCallback
& start_callback
) override
;
192 void GetPage(int page_number
,
193 const GetPageCallback
& get_page_callback
) override
;
195 // Helps to cancel callbacks if this object is destroyed.
196 void RunCallback(const base::Closure
& callback
);
199 scoped_refptr
<PdfToEmfUtilityProcessHostClient
> utility_client_
;
200 base::WeakPtrFactory
<PdfToEmfConverterImpl
> weak_ptr_factory_
;
202 DISALLOW_COPY_AND_ASSIGN(PdfToEmfConverterImpl
);
205 ScopedTempFile
CreateTempFile(scoped_refptr
<RefCountedTempDir
>* temp_dir
) {
206 if (!temp_dir
->get())
207 *temp_dir
= new RefCountedTempDir();
209 if (!(*temp_dir
)->IsValid())
212 if (!base::CreateTemporaryFileInDir((*temp_dir
)->GetPath(), &path
)) {
213 PLOG(ERROR
) << "Failed to create file in "
214 << (*temp_dir
)->GetPath().value();
217 file
.reset(new base::File(path
,
218 base::File::FLAG_CREATE_ALWAYS
|
219 base::File::FLAG_WRITE
|
220 base::File::FLAG_READ
|
221 base::File::FLAG_DELETE_ON_CLOSE
|
222 base::File::FLAG_TEMPORARY
));
223 if (!file
->IsValid()) {
224 PLOG(ERROR
) << "Failed to create " << path
.value();
230 ScopedTempFile
CreateTempPdfFile(
231 const scoped_refptr
<base::RefCountedMemory
>& data
,
232 scoped_refptr
<RefCountedTempDir
>* temp_dir
) {
233 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
235 ScopedTempFile pdf_file
= CreateTempFile(temp_dir
);
237 static_cast<int>(data
->size()) !=
238 pdf_file
->WriteAtCurrentPos(data
->front_as
<char>(), data
->size())) {
240 return pdf_file
.Pass();
242 pdf_file
->Seek(base::File::FROM_BEGIN
, 0);
243 return pdf_file
.Pass();
246 bool LazyEmf::SafePlayback(HDC hdc
) const {
248 bool result
= LoadEmf(&emf
) && emf
.SafePlayback(hdc
);
249 // TODO(vitalybuka): Fix destruction of metafiles. For some reasons
250 // instances of Emf are not deleted. crbug.com/411683
251 // It's known that the Emf going to be played just once to a printer. So just
252 // release file here.
257 bool LazyEmf::SaveTo(base::File
* file
) const {
259 return LoadEmf(&emf
) && emf
.SaveTo(file
);
262 void LazyEmf::Close() const {
267 bool LazyEmf::LoadEmf(Emf
* emf
) const {
268 file_
->Seek(base::File::FROM_BEGIN
, 0);
269 int64 size
= file_
->GetLength();
272 std::vector
<char> data(size
);
273 if (file_
->ReadAtCurrentPos(data
.data(), data
.size()) != size
)
275 return emf
->InitFromData(data
.data(), data
.size());
278 PdfToEmfUtilityProcessHostClient::PdfToEmfUtilityProcessHostClient(
279 base::WeakPtr
<PdfToEmfConverterImpl
> converter
,
280 const PdfRenderSettings
& settings
)
281 : converter_(converter
), settings_(settings
) {
284 PdfToEmfUtilityProcessHostClient::~PdfToEmfUtilityProcessHostClient() {
287 void PdfToEmfUtilityProcessHostClient::Start(
288 const scoped_refptr
<base::RefCountedMemory
>& data
,
289 const PdfToEmfConverter::StartCallback
& start_callback
) {
290 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
291 BrowserThread::PostTask(BrowserThread::IO
,
293 base::Bind(&PdfToEmfUtilityProcessHostClient::Start
,
301 // Store callback before any OnFailed() call to make it called on failure.
302 start_callback_
= start_callback
;
304 // NOTE: This process _must_ be sandboxed, otherwise the pdf dll will load
305 // gdiplus.dll, change how rendering happens, and not be able to correctly
306 // generate when sent to a metafile DC.
307 utility_process_host_
=
308 content::UtilityProcessHost::Create(
309 this, base::MessageLoop::current()->task_runner())->AsWeakPtr();
310 utility_process_host_
->SetName(l10n_util::GetStringUTF16(
311 IDS_UTILITY_PROCESS_EMF_CONVERTOR_NAME
));
312 // Should reply with OnProcessStarted().
313 Send(new ChromeUtilityMsg_StartupPing
);
316 void PdfToEmfUtilityProcessHostClient::OnProcessStarted() {
317 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
318 if (!utility_process_host_
)
321 scoped_refptr
<base::RefCountedMemory
> data
= data_
;
323 BrowserThread::PostTaskAndReplyWithResult(
326 base::Bind(&CreateTempPdfFile
, data
, &temp_dir_
),
327 base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempPdfReady
, this));
330 void PdfToEmfUtilityProcessHostClient::OnTempPdfReady(ScopedTempFile pdf
) {
331 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
332 if (!utility_process_host_
|| !pdf
)
334 base::ProcessHandle process
= utility_process_host_
->GetData().handle
;
335 // Should reply with OnPageCount().
336 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles(
337 IPC::GetFileHandleForProcess(pdf
->GetPlatformFile(), process
, false),
341 void PdfToEmfUtilityProcessHostClient::OnPageCount(int page_count
) {
342 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
343 if (start_callback_
.is_null())
345 BrowserThread::PostTask(BrowserThread::UI
,
347 base::Bind(&PdfToEmfConverterImpl::RunCallback
,
349 base::Bind(start_callback_
, page_count
)));
350 start_callback_
.Reset();
353 void PdfToEmfUtilityProcessHostClient::GetPage(
355 const PdfToEmfConverter::GetPageCallback
& get_page_callback
) {
356 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
357 BrowserThread::PostTask(
360 base::Bind(&PdfToEmfUtilityProcessHostClient::GetPage
,
367 // Store callback before any OnFailed() call to make it called on failure.
368 get_page_callbacks_
.push(GetPageCallbackData(page_number
, get_page_callback
));
370 if (!utility_process_host_
)
373 BrowserThread::PostTaskAndReplyWithResult(
376 base::Bind(&CreateTempFile
, &temp_dir_
),
377 base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempEmfReady
,
379 &get_page_callbacks_
.back()));
382 void PdfToEmfUtilityProcessHostClient::OnTempEmfReady(
383 GetPageCallbackData
* callback_data
,
384 ScopedTempFile emf
) {
385 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
386 if (!utility_process_host_
|| !emf
)
388 base::ProcessHandle process
= utility_process_host_
->GetData().handle
;
389 IPC::PlatformFileForTransit transit
=
390 IPC::GetFileHandleForProcess(emf
->GetPlatformFile(), process
, false);
391 callback_data
->set_emf(emf
.Pass());
392 // Should reply with OnPageDone().
393 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage(
394 callback_data
->page_number(), transit
));
397 void PdfToEmfUtilityProcessHostClient::OnPageDone(bool success
,
398 float scale_factor
) {
399 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
400 if (get_page_callbacks_
.empty())
402 GetPageCallbackData
& data
= get_page_callbacks_
.front();
403 scoped_ptr
<MetafilePlayer
> emf
;
406 ScopedTempFile temp_emf
= data
.TakeEmf();
407 if (!temp_emf
) // Unexpected message from utility process.
409 emf
.reset(new LazyEmf(temp_dir_
, temp_emf
.Pass()));
412 BrowserThread::PostTask(BrowserThread::UI
,
414 base::Bind(&PdfToEmfConverterImpl::RunCallback
,
416 base::Bind(data
.callback(),
419 base::Passed(&emf
))));
420 get_page_callbacks_
.pop();
423 void PdfToEmfUtilityProcessHostClient::Stop() {
424 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
425 BrowserThread::PostTask(
428 base::Bind(&PdfToEmfUtilityProcessHostClient::Stop
, this));
431 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop());
434 void PdfToEmfUtilityProcessHostClient::OnProcessCrashed(int exit_code
) {
438 void PdfToEmfUtilityProcessHostClient::OnProcessLaunchFailed() {
442 bool PdfToEmfUtilityProcessHostClient::OnMessageReceived(
443 const IPC::Message
& message
) {
445 IPC_BEGIN_MESSAGE_MAP(PdfToEmfUtilityProcessHostClient
, message
)
446 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ProcessStarted
, OnProcessStarted
)
448 ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount
, OnPageCount
)
449 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone
,
451 IPC_MESSAGE_UNHANDLED(handled
= false)
452 IPC_END_MESSAGE_MAP()
456 bool PdfToEmfUtilityProcessHostClient::Send(IPC::Message
* msg
) {
457 if (utility_process_host_
)
458 return utility_process_host_
->Send(msg
);
463 void PdfToEmfUtilityProcessHostClient::OnFailed() {
464 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
465 if (!start_callback_
.is_null())
467 while (!get_page_callbacks_
.empty())
468 OnPageDone(false, 0.0f
);
469 utility_process_host_
.reset();
472 PdfToEmfConverterImpl::PdfToEmfConverterImpl() : weak_ptr_factory_(this) {
475 PdfToEmfConverterImpl::~PdfToEmfConverterImpl() {
476 if (utility_client_
.get())
477 utility_client_
->Stop();
480 void PdfToEmfConverterImpl::Start(
481 const scoped_refptr
<base::RefCountedMemory
>& data
,
482 const PdfRenderSettings
& conversion_settings
,
483 const StartCallback
& start_callback
) {
484 DCHECK(!utility_client_
.get());
485 utility_client_
= new PdfToEmfUtilityProcessHostClient(
486 weak_ptr_factory_
.GetWeakPtr(), conversion_settings
);
487 utility_client_
->Start(data
, start_callback
);
490 void PdfToEmfConverterImpl::GetPage(int page_number
,
491 const GetPageCallback
& get_page_callback
) {
492 utility_client_
->GetPage(page_number
, get_page_callback
);
495 void PdfToEmfConverterImpl::RunCallback(const base::Closure
& callback
) {
496 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
502 PdfToEmfConverter::~PdfToEmfConverter() {
506 scoped_ptr
<PdfToEmfConverter
> PdfToEmfConverter::CreateDefault() {
507 return scoped_ptr
<PdfToEmfConverter
>(new PdfToEmfConverterImpl());
510 } // namespace printing