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 "base/bind_helpers.h"
8#include "base/cancelable_callback.h"
9#include "base/file_util.h"
10#include "base/files/file.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/page_range.h"
20#include "printing/pdf_render_settings.h"
21
22namespace printing {
23
24namespace {
25
26using content::BrowserThread;
27
28class FileHandlers {
29 public:
30  FileHandlers() {}
31
32  ~FileHandlers() {
33    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
34  }
35
36  void Init(base::RefCountedMemory* data);
37  bool IsValid();
38
39  base::FilePath GetEmfPath() const {
40    return temp_dir_.path().AppendASCII("output.emf");
41  }
42
43  base::FilePath GetPdfPath() const {
44    return temp_dir_.path().AppendASCII("input.pdf");
45  }
46
47  IPC::PlatformFileForTransit GetPdfForProcess(base::ProcessHandle process) {
48    DCHECK(pdf_file_.IsValid());
49    IPC::PlatformFileForTransit transit =
50        IPC::TakeFileHandleForProcess(pdf_file_.Pass(), process);
51    return transit;
52  }
53
54  const base::FilePath& GetBasePath() const {
55    return temp_dir_.path();
56  }
57
58 private:
59  base::ScopedTempDir temp_dir_;
60  base::File pdf_file_;
61};
62
63void FileHandlers::Init(base::RefCountedMemory* data) {
64  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
65
66  if (!temp_dir_.CreateUniqueTempDir()) {
67    return;
68  }
69
70  if (static_cast<int>(data->size()) !=
71      base::WriteFile(GetPdfPath(), data->front_as<char>(), data->size())) {
72    return;
73  }
74
75  // Reopen in read only mode.
76  pdf_file_.Initialize(GetPdfPath(),
77                       base::File::FLAG_OPEN | base::File::FLAG_READ);
78}
79
80bool FileHandlers::IsValid() {
81  return pdf_file_.IsValid();
82}
83
84// Converts PDF into EMF.
85// Class uses 3 threads: UI, IO and FILE.
86// Internal workflow is following:
87// 1. Create instance on the UI thread. (files_, settings_,)
88// 2. Create file on the FILE thread.
89// 3. Start utility process and start conversion on the IO thread.
90// 4. Run result callback on the UI thread.
91// 5. Instance is destroyed from any thread that has the last reference.
92// 6. FileHandlers destroyed on the FILE thread.
93//    This step posts |FileHandlers| to be destroyed on the FILE thread.
94// All these steps work sequentially, so no data should be accessed
95// simultaneously by several threads.
96class PdfToEmfUtilityProcessHostClient
97    : public content::UtilityProcessHostClient {
98 public:
99  explicit PdfToEmfUtilityProcessHostClient(
100      const printing::PdfRenderSettings& settings);
101
102  void Convert(base::RefCountedMemory* data,
103               const PdfToEmfConverter::ResultCallback& callback);
104
105  // UtilityProcessHostClient implementation.
106  virtual void OnProcessCrashed(int exit_code) OVERRIDE;
107  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
108
109 private:
110  virtual ~PdfToEmfUtilityProcessHostClient();
111
112  // Message handlers.
113  void OnProcessStarted();
114  void OnSucceeded(const std::vector<printing::PageRange>& page_ranges,
115                   double scale_factor);
116  void OnFailed();
117
118  void RunCallback(const std::vector<printing::PageRange>& page_ranges,
119                   double scale_factor);
120
121  void StartProcessOnIOThread();
122
123  void RunCallbackOnUIThread(
124      const std::vector<printing::PageRange>& page_ranges,
125      double scale_factor);
126  void OnFilesReadyOnUIThread();
127
128  scoped_ptr<FileHandlers> files_;
129  printing::PdfRenderSettings settings_;
130  PdfToEmfConverter::ResultCallback callback_;
131  base::WeakPtr<content::UtilityProcessHost> utility_process_host_;
132
133  DISALLOW_COPY_AND_ASSIGN(PdfToEmfUtilityProcessHostClient);
134};
135
136PdfToEmfUtilityProcessHostClient::PdfToEmfUtilityProcessHostClient(
137    const printing::PdfRenderSettings& settings)
138    : settings_(settings) {}
139
140PdfToEmfUtilityProcessHostClient::~PdfToEmfUtilityProcessHostClient() {
141  // Delete temp directory.
142  BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, files_.release());
143}
144
145void PdfToEmfUtilityProcessHostClient::Convert(
146    base::RefCountedMemory* data,
147    const PdfToEmfConverter::ResultCallback& callback) {
148  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
149  callback_ = callback;
150  CHECK(!files_);
151  files_.reset(new FileHandlers());
152  BrowserThread::PostTaskAndReply(
153      BrowserThread::FILE,
154      FROM_HERE,
155      base::Bind(&FileHandlers::Init,
156                 base::Unretained(files_.get()),
157                 make_scoped_refptr(data)),
158      base::Bind(&PdfToEmfUtilityProcessHostClient::OnFilesReadyOnUIThread,
159                 this));
160}
161
162void PdfToEmfUtilityProcessHostClient::OnProcessCrashed(int exit_code) {
163  OnFailed();
164}
165
166bool PdfToEmfUtilityProcessHostClient::OnMessageReceived(
167  const IPC::Message& message) {
168  bool handled = true;
169  IPC_BEGIN_MESSAGE_MAP(PdfToEmfUtilityProcessHostClient, message)
170    IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ProcessStarted, OnProcessStarted)
171    IPC_MESSAGE_HANDLER(
172        ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_Succeeded, OnSucceeded)
173    IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Failed,
174                        OnFailed)
175    IPC_MESSAGE_UNHANDLED(handled = false)
176  IPC_END_MESSAGE_MAP()
177  return handled;
178}
179
180void PdfToEmfUtilityProcessHostClient::OnProcessStarted() {
181  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
182  if (!utility_process_host_) {
183    RunCallbackOnUIThread(std::vector<printing::PageRange>(), 0.0);
184    return;
185  }
186
187  base::ProcessHandle process = utility_process_host_->GetData().handle;
188  utility_process_host_->Send(
189      new ChromeUtilityMsg_RenderPDFPagesToMetafiles(
190          files_->GetPdfForProcess(process),
191          files_->GetEmfPath(),
192          settings_,
193          std::vector<printing::PageRange>()));
194  utility_process_host_.reset();
195}
196
197void PdfToEmfUtilityProcessHostClient::OnSucceeded(
198    const std::vector<printing::PageRange>& page_ranges,
199    double scale_factor) {
200  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
201  RunCallback(page_ranges, scale_factor);
202}
203
204void PdfToEmfUtilityProcessHostClient::OnFailed() {
205  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
206  RunCallback(std::vector<printing::PageRange>(), 0.0);
207}
208
209void PdfToEmfUtilityProcessHostClient::OnFilesReadyOnUIThread() {
210  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
211  if (!files_->IsValid()) {
212    RunCallbackOnUIThread(std::vector<printing::PageRange>(), 0.0);
213    return;
214  }
215  BrowserThread::PostTask(
216      BrowserThread::IO,
217      FROM_HERE,
218      base::Bind(&PdfToEmfUtilityProcessHostClient::StartProcessOnIOThread,
219                 this));
220}
221
222void PdfToEmfUtilityProcessHostClient::StartProcessOnIOThread() {
223  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
224  utility_process_host_ =
225      content::UtilityProcessHost::Create(
226          this,
227          base::MessageLoop::current()->message_loop_proxy())->AsWeakPtr();
228  // NOTE: This process _must_ be sandboxed, otherwise the pdf dll will load
229  // gdiplus.dll, change how rendering happens, and not be able to correctly
230  // generate when sent to a metafile DC.
231  utility_process_host_->SetExposedDir(files_->GetBasePath());
232  utility_process_host_->Send(new ChromeUtilityMsg_StartupPing);
233}
234
235void PdfToEmfUtilityProcessHostClient::RunCallback(
236    const std::vector<printing::PageRange>& page_ranges,
237    double scale_factor) {
238  BrowserThread::PostTask(
239      BrowserThread::UI,
240      FROM_HERE,
241      base::Bind(&PdfToEmfUtilityProcessHostClient::RunCallbackOnUIThread,
242                 this,
243                 page_ranges,
244                 scale_factor));
245}
246
247void PdfToEmfUtilityProcessHostClient::RunCallbackOnUIThread(
248    const std::vector<printing::PageRange>& page_ranges,
249    double scale_factor) {
250  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
251  std::vector<base::FilePath> page_filenames;
252  std::vector<printing::PageRange>::const_iterator iter;
253  for (iter = page_ranges.begin(); iter != page_ranges.end(); ++iter) {
254    for (int page_number = iter->from; page_number <= iter->to; ++page_number) {
255      page_filenames.push_back(files_->GetEmfPath().InsertBeforeExtensionASCII(
256          base::StringPrintf(".%d", page_number)));
257    }
258  }
259  if (!callback_.is_null()) {
260    BrowserThread::PostTask(
261        BrowserThread::UI,
262        FROM_HERE,
263        base::Bind(callback_, scale_factor, page_filenames));
264    callback_.Reset();
265  }
266}
267
268class PdfToEmfConverterImpl : public PdfToEmfConverter {
269 public:
270  PdfToEmfConverterImpl();
271
272  virtual ~PdfToEmfConverterImpl();
273
274  virtual void Start(base::RefCountedMemory* data,
275                     const printing::PdfRenderSettings& conversion_settings,
276                     const ResultCallback& callback) OVERRIDE;
277
278 private:
279  scoped_refptr<PdfToEmfUtilityProcessHostClient> utility_client_;
280  base::CancelableCallback<ResultCallback::RunType> callback_;
281
282  DISALLOW_COPY_AND_ASSIGN(PdfToEmfConverterImpl);
283};
284
285PdfToEmfConverterImpl::PdfToEmfConverterImpl() {
286}
287
288PdfToEmfConverterImpl::~PdfToEmfConverterImpl() {
289}
290
291void PdfToEmfConverterImpl::Start(
292    base::RefCountedMemory* data,
293    const printing::PdfRenderSettings& conversion_settings,
294    const ResultCallback& callback) {
295  // Rebind cancelable callback to avoid calling callback if
296  // PdfToEmfConverterImpl is destroyed.
297  callback_.Reset(callback);
298  utility_client_ = new PdfToEmfUtilityProcessHostClient(conversion_settings);
299  utility_client_->Convert(data, callback_.callback());
300}
301
302}  // namespace
303
304// static
305scoped_ptr<PdfToEmfConverter> PdfToEmfConverter::CreateDefault() {
306  return scoped_ptr<PdfToEmfConverter>(new PdfToEmfConverterImpl());
307}
308
309}  // namespace printing
310