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 22 namespace printing { 23 24 namespace { 25 26 using content::BrowserThread; 27 28 class 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. 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(); } 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 51 typedef 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. 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(); } 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. 90 class 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 178 class 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 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(); 219 } 220 221 ScopedTempFile 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 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; 245 } 246 247 bool LazyEmf::SaveTo(base::File* file) const { 248 Emf emf; 249 return LoadEmf(&emf) && emf.SaveTo(file); 250 } 251 252 void LazyEmf::Close() const { 253 file_.reset(); 254 temp_dir_ = NULL; 255 } 256 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()); 266 } 267 268 PdfToEmfUtilityProcessHostClient::PdfToEmfUtilityProcessHostClient( 269 base::WeakPtr<PdfToEmfConverterImpl> converter, 270 const PdfRenderSettings& settings) 271 : converter_(converter), settings_(settings) { 272 } 273 274 PdfToEmfUtilityProcessHostClient::~PdfToEmfUtilityProcessHostClient() { 275 } 276 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; 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 307 void 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 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_)); 330 } 331 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(); 342 } 343 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; 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 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)); 386 } 387 388 void 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 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; 415 } 416 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop()); 417 } 418 419 void PdfToEmfUtilityProcessHostClient::OnProcessCrashed(int exit_code) { 420 OnFailed(); 421 } 422 423 void PdfToEmfUtilityProcessHostClient::OnProcessLaunchFailed() { 424 OnFailed(); 425 } 426 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; 439 } 440 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; 446 } 447 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.0); 454 utility_process_host_.reset(); 455 } 456 457 PdfToEmfConverterImpl::PdfToEmfConverterImpl() : weak_ptr_factory_(this) { 458 } 459 460 PdfToEmfConverterImpl::~PdfToEmfConverterImpl() { 461 if (utility_client_.get()) 462 utility_client_->Stop(); 463 } 464 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); 473 } 474 475 void PdfToEmfConverterImpl::GetPage(int page_number, 476 const GetPageCallback& get_page_callback) { 477 utility_client_->GetPage(page_number, get_page_callback); 478 } 479 480 void PdfToEmfConverterImpl::RunCallback(const base::Closure& callback) { 481 DCHECK_CURRENTLY_ON(BrowserThread::UI); 482 callback.Run(); 483 } 484 485 } // namespace 486 487 PdfToEmfConverter::~PdfToEmfConverter() { 488 } 489 490 // static 491 scoped_ptr<PdfToEmfConverter> PdfToEmfConverter::CreateDefault() { 492 return scoped_ptr<PdfToEmfConverter>(new PdfToEmfConverterImpl()); 493 } 494 495 } // namespace printing 496