Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / printing / pdf_to_emf_converter.cc
blob48a3845730551460d99ae2dfcf4417a4ba1d2907
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"
7 #include <queue>
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"
22 namespace printing {
24 namespace {
26 using content::BrowserThread;
28 class PdfToEmfConverterImpl;
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.
34 class 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(); }
42 private:
43 friend struct BrowserThread::DeleteOnThread<BrowserThread::FILE>;
44 friend class base::DeleteHelper<RefCountedTempDir>;
45 ~RefCountedTempDir() {}
47 base::ScopedTempDir temp_dir_;
48 DISALLOW_COPY_AND_ASSIGN(RefCountedTempDir);
51 typedef scoped_ptr<base::File, BrowserThread::DeleteOnFileThread>
52 ScopedTempFile;
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.
58 class 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(); }
64 virtual bool SafePlayback(HDC hdc) const override;
65 virtual bool SaveTo(base::File* file) const override;
67 private:
68 void Close() const;
69 bool LoadEmf(Emf* emf) const;
71 mutable scoped_refptr<RefCountedTempDir> temp_dir_;
72 mutable ScopedTempFile file_; // Mutable because of consts in base class.
74 DISALLOW_COPY_AND_ASSIGN(LazyEmf);
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.
88 // All these steps work sequentially, so no data should be accessed
89 // simultaneously by several threads.
90 class PdfToEmfUtilityProcessHostClient
91 : public content::UtilityProcessHostClient {
92 public:
93 PdfToEmfUtilityProcessHostClient(
94 base::WeakPtr<PdfToEmfConverterImpl> converter,
95 const PdfRenderSettings& settings);
97 void Start(const scoped_refptr<base::RefCountedMemory>& data,
98 const PdfToEmfConverter::StartCallback& start_callback);
100 void GetPage(int page_number,
101 const PdfToEmfConverter::GetPageCallback& get_page_callback);
103 void Stop();
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;
110 private:
111 class GetPageCallbackData {
112 MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData, RValue);
114 public:
115 GetPageCallbackData(int page_number,
116 PdfToEmfConverter::GetPageCallback callback)
117 : page_number_(page_number), callback_(callback) {}
119 // Move constructor for STL.
120 GetPageCallbackData(RValue other) { this->operator=(other); }
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;
130 int page_number() const { return page_number_; }
131 const PdfToEmfConverter::GetPageCallback& callback() const {
132 return callback_;
134 ScopedTempFile emf() { return emf_.Pass(); }
135 void set_emf(ScopedTempFile emf) { emf_ = emf.Pass(); }
137 private:
138 int page_number_;
139 PdfToEmfConverter::GetPageCallback callback_;
140 ScopedTempFile emf_;
143 virtual ~PdfToEmfUtilityProcessHostClient();
145 bool Send(IPC::Message* msg);
147 // Message handlers.
148 void OnProcessStarted();
149 void OnPageCount(int page_count);
150 void OnPageDone(bool success, float scale_factor);
152 void OnFailed();
153 void OnTempPdfReady(ScopedTempFile pdf);
154 void OnTempEmfReady(GetPageCallbackData* callback_data, ScopedTempFile emf);
156 scoped_refptr<RefCountedTempDir> temp_dir_;
158 // Used to suppress callbacks after PdfToEmfConverterImpl is deleted.
159 base::WeakPtr<PdfToEmfConverterImpl> converter_;
160 PdfRenderSettings settings_;
161 scoped_refptr<base::RefCountedMemory> data_;
163 // Document loaded callback.
164 PdfToEmfConverter::StartCallback start_callback_;
166 // Process host for IPC.
167 base::WeakPtr<content::UtilityProcessHost> utility_process_host_;
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_;
175 DISALLOW_COPY_AND_ASSIGN(PdfToEmfUtilityProcessHostClient);
178 class PdfToEmfConverterImpl : public PdfToEmfConverter {
179 public:
180 PdfToEmfConverterImpl();
182 virtual ~PdfToEmfConverterImpl();
184 virtual void Start(const scoped_refptr<base::RefCountedMemory>& data,
185 const PdfRenderSettings& conversion_settings,
186 const StartCallback& start_callback) override;
188 virtual void GetPage(int page_number,
189 const GetPageCallback& get_page_callback) override;
191 // Helps to cancel callbacks if this object is destroyed.
192 void RunCallback(const base::Closure& callback);
194 private:
195 scoped_refptr<PdfToEmfUtilityProcessHostClient> utility_client_;
196 base::WeakPtrFactory<PdfToEmfConverterImpl> weak_ptr_factory_;
198 DISALLOW_COPY_AND_ASSIGN(PdfToEmfConverterImpl);
201 ScopedTempFile 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();
221 ScopedTempFile CreateTempPdfFile(
222 const scoped_refptr<base::RefCountedMemory>& data,
223 scoped_refptr<RefCountedTempDir>* temp_dir) {
224 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
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();
232 pdf_file->Seek(base::File::FROM_BEGIN, 0);
233 return pdf_file.Pass();
236 bool 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;
247 bool LazyEmf::SaveTo(base::File* file) const {
248 Emf emf;
249 return LoadEmf(&emf) && emf.SaveTo(file);
252 void LazyEmf::Close() const {
253 file_.reset();
254 temp_dir_ = NULL;
257 bool 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());
268 PdfToEmfUtilityProcessHostClient::PdfToEmfUtilityProcessHostClient(
269 base::WeakPtr<PdfToEmfConverterImpl> converter,
270 const PdfRenderSettings& settings)
271 : converter_(converter), settings_(settings) {
274 PdfToEmfUtilityProcessHostClient::~PdfToEmfUtilityProcessHostClient() {
277 void 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;
289 data_ = data;
291 // Store callback before any OnFailed() call to make it called on failure.
292 start_callback_ = start_callback;
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);
307 void PdfToEmfUtilityProcessHostClient::OnProcessStarted() {
308 DCHECK_CURRENTLY_ON(BrowserThread::IO);
309 if (!utility_process_host_)
310 return OnFailed();
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));
321 void 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_));
332 void 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();
344 void 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;
358 // Store callback before any OnFailed() call to make it called on failure.
359 get_page_callbacks_.push(GetPageCallbackData(page_number, get_page_callback));
361 if (!utility_process_host_)
362 return OnFailed();
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()));
373 void 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));
388 void PdfToEmfUtilityProcessHostClient::OnPageDone(bool success,
389 float 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();
408 void 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;
416 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop());
419 void PdfToEmfUtilityProcessHostClient::OnProcessCrashed(int exit_code) {
420 OnFailed();
423 void PdfToEmfUtilityProcessHostClient::OnProcessLaunchFailed() {
424 OnFailed();
427 bool 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;
441 bool PdfToEmfUtilityProcessHostClient::Send(IPC::Message* msg) {
442 if (utility_process_host_)
443 return utility_process_host_->Send(msg);
444 delete msg;
445 return false;
448 void 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.0f);
454 utility_process_host_.reset();
457 PdfToEmfConverterImpl::PdfToEmfConverterImpl() : weak_ptr_factory_(this) {
460 PdfToEmfConverterImpl::~PdfToEmfConverterImpl() {
461 if (utility_client_.get())
462 utility_client_->Stop();
465 void 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);
475 void PdfToEmfConverterImpl::GetPage(int page_number,
476 const GetPageCallback& get_page_callback) {
477 utility_client_->GetPage(page_number, get_page_callback);
480 void PdfToEmfConverterImpl::RunCallback(const base::Closure& callback) {
481 DCHECK_CURRENTLY_ON(BrowserThread::UI);
482 callback.Run();
485 } // namespace
487 PdfToEmfConverter::~PdfToEmfConverter() {
490 // static
491 scoped_ptr<PdfToEmfConverter> PdfToEmfConverter::CreateDefault() {
492 return scoped_ptr<PdfToEmfConverter>(new PdfToEmfConverterImpl());
495 } // namespace printing