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.
4
5#include "chrome/browser/printing/pdf_to_emf_converter.h"
6
7#include <queue>
8
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 "content/public/browser/browser_thread.h"
16#include "content/public/browser/child_process_data.h"
17#include "content/public/browser/utility_process_host.h"
18#include "content/public/browser/utility_process_host_client.h"
19#include "printing/emf_win.h"
20#include "printing/pdf_render_settings.h"
21
22namespace printing {
23
24namespace {
25
26using content::BrowserThread;
27
28class PdfToEmfConverterImpl;
29
30// Allows to delete temporary directory after all temporary files created inside
31// are closed. Windows cannot delete directory with opened files. Directory is
32// used to store PDF and metafiles. PDF should be gone by the time utility
33// process exits. Metafiles should be gone when all LazyEmf destroyed.
34class RefCountedTempDir
35    : public base::RefCountedThreadSafe<RefCountedTempDir,
36                                        BrowserThread::DeleteOnFileThread> {
37 public:
38  RefCountedTempDir() { ignore_result(temp_dir_.CreateUniqueTempDir()); }
39  bool IsValid() const { return temp_dir_.IsValid(); }
40  const base::FilePath& GetPath() const { return temp_dir_.path(); }
41
42 private:
43  friend struct BrowserThread::DeleteOnThread<BrowserThread::FILE>;
44  friend class base::DeleteHelper<RefCountedTempDir>;
45  ~RefCountedTempDir() {}
46
47  base::ScopedTempDir temp_dir_;
48  DISALLOW_COPY_AND_ASSIGN(RefCountedTempDir);
49};
50
51typedef scoped_ptr<base::File, BrowserThread::DeleteOnFileThread>
52    ScopedTempFile;
53
54// Wrapper for Emf to keep only file handle in memory, and load actual data only
55// on playback. Emf::InitFromFile() can play metafile directly from disk, but it
56// can't open file handles. We need file handles to reliably delete temporary
57// files, and to efficiently interact with utility process.
58class LazyEmf : public MetafilePlayer {
59 public:
60  LazyEmf(const scoped_refptr<RefCountedTempDir>& temp_dir, ScopedTempFile file)
61      : temp_dir_(temp_dir), file_(file.Pass()) {}
62  virtual ~LazyEmf() { Close(); }
63
64  virtual bool SafePlayback(HDC hdc) const OVERRIDE;
65  virtual bool SaveTo(base::File* file) const OVERRIDE;
66
67 private:
68  void Close() const;
69  bool LoadEmf(Emf* emf) const;
70
71  mutable scoped_refptr<RefCountedTempDir> temp_dir_;
72  mutable ScopedTempFile file_;  // Mutable because of consts in base class.
73
74  DISALLOW_COPY_AND_ASSIGN(LazyEmf);
75};
76
77// Converts PDF into EMF.
78// Class uses 3 threads: UI, IO and FILE.
79// Internal workflow is following:
80// 1. Create instance on the UI thread. (files_, settings_,)
81// 2. Create pdf file on the FILE thread.
82// 3. Start utility process and start conversion on the IO thread.
83// 4. Utility process returns page count.
84// 5. For each page:
85//   1. Clients requests page with file handle to a temp file.
86//   2. Utility converts the page, save it to the file and reply.
87//
88// All these steps work sequentially, so no data should be accessed
89// simultaneously by several threads.
90class PdfToEmfUtilityProcessHostClient
91    : public content::UtilityProcessHostClient {
92 public:
93  PdfToEmfUtilityProcessHostClient(
94      base::WeakPtr<PdfToEmfConverterImpl> converter,
95      const PdfRenderSettings& settings);
96
97  void Start(const scoped_refptr<base::RefCountedMemory>& data,
98             const PdfToEmfConverter::StartCallback& start_callback);
99
100  void GetPage(int page_number,
101               const PdfToEmfConverter::GetPageCallback& get_page_callback);
102
103  void Stop();
104
105  // UtilityProcessHostClient implementation.
106  virtual void OnProcessCrashed(int exit_code) OVERRIDE;
107  virtual void OnProcessLaunchFailed() OVERRIDE;
108  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
109
110 private:
111  class GetPageCallbackData {
112    MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData, RValue);
113
114   public:
115    GetPageCallbackData(int page_number,
116                        PdfToEmfConverter::GetPageCallback callback)
117        : page_number_(page_number), callback_(callback) {}
118
119    // Move constructor for STL.
120    GetPageCallbackData(RValue other) { this->operator=(other); }
121
122    // Move assignment for STL.
123    GetPageCallbackData& operator=(RValue rhs) {
124      page_number_ = rhs.object->page_number_;
125      callback_ = rhs.object->callback_;
126      emf_ = rhs.object->emf_.Pass();
127      return *this;
128    }
129
130    int page_number() const { return page_number_; }
131    const PdfToEmfConverter::GetPageCallback& callback() const {
132      return callback_;
133    }
134    ScopedTempFile emf() { return emf_.Pass(); }
135    void set_emf(ScopedTempFile emf) { emf_ = emf.Pass(); }
136
137   private:
138    int page_number_;
139    PdfToEmfConverter::GetPageCallback callback_;
140    ScopedTempFile emf_;
141  };
142
143  virtual ~PdfToEmfUtilityProcessHostClient();
144
145  bool Send(IPC::Message* msg);
146
147  // Message handlers.
148  void OnProcessStarted();
149  void OnPageCount(int page_count);
150  void OnPageDone(bool success, double scale_factor);
151
152  void OnFailed();
153  void OnTempPdfReady(ScopedTempFile pdf);
154  void OnTempEmfReady(GetPageCallbackData* callback_data, ScopedTempFile emf);
155
156  scoped_refptr<RefCountedTempDir> temp_dir_;
157
158  // Used to suppress callbacks after PdfToEmfConverterImpl is deleted.
159  base::WeakPtr<PdfToEmfConverterImpl> converter_;
160  PdfRenderSettings settings_;
161  scoped_refptr<base::RefCountedMemory> data_;
162
163  // Document loaded callback.
164  PdfToEmfConverter::StartCallback start_callback_;
165
166  // Process host for IPC.
167  base::WeakPtr<content::UtilityProcessHost> utility_process_host_;
168
169  // Queue of callbacks for GetPage() requests. Utility process should reply
170  // with PageDone in the same order as requests were received.
171  // Use containers that keeps element pointers valid after push() and pop().
172  typedef std::queue<GetPageCallbackData> GetPageCallbacks;
173  GetPageCallbacks get_page_callbacks_;
174
175  DISALLOW_COPY_AND_ASSIGN(PdfToEmfUtilityProcessHostClient);
176};
177
178class PdfToEmfConverterImpl : public PdfToEmfConverter {
179 public:
180  PdfToEmfConverterImpl();
181
182  virtual ~PdfToEmfConverterImpl();
183
184  virtual void Start(const scoped_refptr<base::RefCountedMemory>& data,
185                     const PdfRenderSettings& conversion_settings,
186                     const StartCallback& start_callback) OVERRIDE;
187
188  virtual void GetPage(int page_number,
189                       const GetPageCallback& get_page_callback) OVERRIDE;
190
191  // Helps to cancel callbacks if this object is destroyed.
192  void RunCallback(const base::Closure& callback);
193
194 private:
195  scoped_refptr<PdfToEmfUtilityProcessHostClient> utility_client_;
196  base::WeakPtrFactory<PdfToEmfConverterImpl> weak_ptr_factory_;
197
198  DISALLOW_COPY_AND_ASSIGN(PdfToEmfConverterImpl);
199};
200
201ScopedTempFile CreateTempFile(scoped_refptr<RefCountedTempDir>* temp_dir) {
202  if (!temp_dir->get())
203    *temp_dir = new RefCountedTempDir();
204  ScopedTempFile file;
205  if (!(*temp_dir)->IsValid())
206    return file.Pass();
207  base::FilePath path;
208  if (!base::CreateTemporaryFileInDir((*temp_dir)->GetPath(), &path))
209    return file.Pass();
210  file.reset(new base::File(path,
211                            base::File::FLAG_CREATE_ALWAYS |
212                            base::File::FLAG_WRITE |
213                            base::File::FLAG_READ |
214                            base::File::FLAG_DELETE_ON_CLOSE |
215                            base::File::FLAG_TEMPORARY));
216  if (!file->IsValid())
217    file.reset();
218  return file.Pass();
219}
220
221ScopedTempFile CreateTempPdfFile(
222    const scoped_refptr<base::RefCountedMemory>& data,
223    scoped_refptr<RefCountedTempDir>* temp_dir) {
224  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
225
226  ScopedTempFile pdf_file = CreateTempFile(temp_dir);
227  if (!pdf_file ||
228      static_cast<int>(data->size()) !=
229          pdf_file->WriteAtCurrentPos(data->front_as<char>(), data->size())) {
230    pdf_file.reset();
231  }
232  pdf_file->Seek(base::File::FROM_BEGIN, 0);
233  return pdf_file.Pass();
234}
235
236bool LazyEmf::SafePlayback(HDC hdc) const {
237  Emf emf;
238  bool result = LoadEmf(&emf) && emf.SafePlayback(hdc);
239  // TODO(vitalybuka): Fix destruction of metafiles. For some reasons
240  // instances of Emf are not deleted. crbug.com/411683
241  // It's known that the Emf going to be played just once to a printer. So just
242  // release file here.
243  Close();
244  return result;
245}
246
247bool LazyEmf::SaveTo(base::File* file) const {
248  Emf emf;
249  return LoadEmf(&emf) && emf.SaveTo(file);
250}
251
252void LazyEmf::Close() const {
253  file_.reset();
254  temp_dir_ = NULL;
255}
256
257bool LazyEmf::LoadEmf(Emf* emf) const {
258  file_->Seek(base::File::FROM_BEGIN, 0);
259  int64 size = file_->GetLength();
260  if (size <= 0)
261    return false;
262  std::vector<char> data(size);
263  if (file_->ReadAtCurrentPos(data.data(), data.size()) != size)
264    return false;
265  return emf->InitFromData(data.data(), data.size());
266}
267
268PdfToEmfUtilityProcessHostClient::PdfToEmfUtilityProcessHostClient(
269    base::WeakPtr<PdfToEmfConverterImpl> converter,
270    const PdfRenderSettings& settings)
271    : converter_(converter), settings_(settings) {
272}
273
274PdfToEmfUtilityProcessHostClient::~PdfToEmfUtilityProcessHostClient() {
275}
276
277void PdfToEmfUtilityProcessHostClient::Start(
278    const scoped_refptr<base::RefCountedMemory>& data,
279    const PdfToEmfConverter::StartCallback& start_callback) {
280  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
281    BrowserThread::PostTask(BrowserThread::IO,
282                            FROM_HERE,
283                            base::Bind(&PdfToEmfUtilityProcessHostClient::Start,
284                                       this,
285                                       data,
286                                       start_callback));
287    return;
288  }
289  data_ = data;
290
291  // Store callback before any OnFailed() call to make it called on failure.
292  start_callback_ = start_callback;
293
294  // NOTE: This process _must_ be sandboxed, otherwise the pdf dll will load
295  // gdiplus.dll, change how rendering happens, and not be able to correctly
296  // generate when sent to a metafile DC.
297  utility_process_host_ =
298      content::UtilityProcessHost::Create(
299          this, base::MessageLoop::current()->message_loop_proxy())
300          ->AsWeakPtr();
301  if (!utility_process_host_)
302    return OnFailed();
303  // Should reply with OnProcessStarted().
304  Send(new ChromeUtilityMsg_StartupPing);
305}
306
307void PdfToEmfUtilityProcessHostClient::OnProcessStarted() {
308  DCHECK_CURRENTLY_ON(BrowserThread::IO);
309  if (!utility_process_host_)
310    return OnFailed();
311
312  scoped_refptr<base::RefCountedMemory> data = data_;
313  data_ = NULL;
314  BrowserThread::PostTaskAndReplyWithResult(
315      BrowserThread::FILE,
316      FROM_HERE,
317      base::Bind(&CreateTempPdfFile, data, &temp_dir_),
318      base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempPdfReady, this));
319}
320
321void PdfToEmfUtilityProcessHostClient::OnTempPdfReady(ScopedTempFile pdf) {
322  DCHECK_CURRENTLY_ON(BrowserThread::IO);
323  if (!utility_process_host_)
324    return OnFailed();
325  base::ProcessHandle process = utility_process_host_->GetData().handle;
326  // Should reply with OnPageCount().
327  Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles(
328      IPC::GetFileHandleForProcess(pdf->GetPlatformFile(), process, false),
329      settings_));
330}
331
332void PdfToEmfUtilityProcessHostClient::OnPageCount(int page_count) {
333  DCHECK_CURRENTLY_ON(BrowserThread::IO);
334  if (start_callback_.is_null())
335    return OnFailed();
336  BrowserThread::PostTask(BrowserThread::UI,
337                          FROM_HERE,
338                          base::Bind(&PdfToEmfConverterImpl::RunCallback,
339                                     converter_,
340                                     base::Bind(start_callback_, page_count)));
341  start_callback_.Reset();
342}
343
344void PdfToEmfUtilityProcessHostClient::GetPage(
345    int page_number,
346    const PdfToEmfConverter::GetPageCallback& get_page_callback) {
347  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
348    BrowserThread::PostTask(
349        BrowserThread::IO,
350        FROM_HERE,
351        base::Bind(&PdfToEmfUtilityProcessHostClient::GetPage,
352                   this,
353                   page_number,
354                   get_page_callback));
355    return;
356  }
357
358  // Store callback before any OnFailed() call to make it called on failure.
359  get_page_callbacks_.push(GetPageCallbackData(page_number, get_page_callback));
360
361  if (!utility_process_host_)
362    return OnFailed();
363
364  BrowserThread::PostTaskAndReplyWithResult(
365      BrowserThread::FILE,
366      FROM_HERE,
367      base::Bind(&CreateTempFile, &temp_dir_),
368      base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempEmfReady,
369                 this,
370                 &get_page_callbacks_.back()));
371}
372
373void PdfToEmfUtilityProcessHostClient::OnTempEmfReady(
374    GetPageCallbackData* callback_data,
375    ScopedTempFile emf) {
376  DCHECK_CURRENTLY_ON(BrowserThread::IO);
377  if (!utility_process_host_)
378    return OnFailed();
379  base::ProcessHandle process = utility_process_host_->GetData().handle;
380  IPC::PlatformFileForTransit transit =
381      IPC::GetFileHandleForProcess(emf->GetPlatformFile(), process, false);
382  callback_data->set_emf(emf.Pass());
383  // Should reply with OnPageDone().
384  Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage(
385      callback_data->page_number(), transit));
386}
387
388void PdfToEmfUtilityProcessHostClient::OnPageDone(bool success,
389                                                  double scale_factor) {
390  DCHECK_CURRENTLY_ON(BrowserThread::IO);
391  if (get_page_callbacks_.empty())
392    return OnFailed();
393  scoped_ptr<MetafilePlayer> emf;
394  GetPageCallbackData& data = get_page_callbacks_.front();
395  if (success)
396    emf.reset(new LazyEmf(temp_dir_, data.emf().Pass()));
397  BrowserThread::PostTask(BrowserThread::UI,
398                          FROM_HERE,
399                          base::Bind(&PdfToEmfConverterImpl::RunCallback,
400                                     converter_,
401                                     base::Bind(data.callback(),
402                                                data.page_number(),
403                                                scale_factor,
404                                                base::Passed(&emf))));
405  get_page_callbacks_.pop();
406}
407
408void PdfToEmfUtilityProcessHostClient::Stop() {
409  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
410    BrowserThread::PostTask(
411        BrowserThread::IO,
412        FROM_HERE,
413        base::Bind(&PdfToEmfUtilityProcessHostClient::Stop, this));
414    return;
415  }
416  Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop());
417}
418
419void PdfToEmfUtilityProcessHostClient::OnProcessCrashed(int exit_code) {
420  OnFailed();
421}
422
423void PdfToEmfUtilityProcessHostClient::OnProcessLaunchFailed() {
424  OnFailed();
425}
426
427bool PdfToEmfUtilityProcessHostClient::OnMessageReceived(
428    const IPC::Message& message) {
429  bool handled = true;
430  IPC_BEGIN_MESSAGE_MAP(PdfToEmfUtilityProcessHostClient, message)
431    IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ProcessStarted, OnProcessStarted)
432    IPC_MESSAGE_HANDLER(
433        ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount, OnPageCount)
434    IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone,
435                        OnPageDone)
436    IPC_MESSAGE_UNHANDLED(handled = false)
437  IPC_END_MESSAGE_MAP()
438  return handled;
439}
440
441bool PdfToEmfUtilityProcessHostClient::Send(IPC::Message* msg) {
442  if (utility_process_host_)
443    return utility_process_host_->Send(msg);
444  delete msg;
445  return false;
446}
447
448void PdfToEmfUtilityProcessHostClient::OnFailed() {
449  DCHECK_CURRENTLY_ON(BrowserThread::IO);
450  if (!start_callback_.is_null())
451    OnPageCount(0);
452  while (!get_page_callbacks_.empty())
453    OnPageDone(false, 0.0);
454  utility_process_host_.reset();
455}
456
457PdfToEmfConverterImpl::PdfToEmfConverterImpl() : weak_ptr_factory_(this) {
458}
459
460PdfToEmfConverterImpl::~PdfToEmfConverterImpl() {
461  if (utility_client_.get())
462    utility_client_->Stop();
463}
464
465void PdfToEmfConverterImpl::Start(
466    const scoped_refptr<base::RefCountedMemory>& data,
467    const PdfRenderSettings& conversion_settings,
468    const StartCallback& start_callback) {
469  DCHECK(!utility_client_.get());
470  utility_client_ = new PdfToEmfUtilityProcessHostClient(
471      weak_ptr_factory_.GetWeakPtr(), conversion_settings);
472  utility_client_->Start(data, start_callback);
473}
474
475void PdfToEmfConverterImpl::GetPage(int page_number,
476                                    const GetPageCallback& get_page_callback) {
477  utility_client_->GetPage(page_number, get_page_callback);
478}
479
480void PdfToEmfConverterImpl::RunCallback(const base::Closure& callback) {
481  DCHECK_CURRENTLY_ON(BrowserThread::UI);
482  callback.Run();
483}
484
485}  // namespace
486
487PdfToEmfConverter::~PdfToEmfConverter() {
488}
489
490// static
491scoped_ptr<PdfToEmfConverter> PdfToEmfConverter::CreateDefault() {
492  return scoped_ptr<PdfToEmfConverter>(new PdfToEmfConverterImpl());
493}
494
495}  // namespace printing
496