1 // Copyright (c) 2012 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/print_job.h"
8 #include "base/bind_helpers.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/run_loop.h"
11 #include "base/threading/thread_restrictions.h"
12 #include "base/threading/thread_task_runner_handle.h"
13 #include "base/threading/worker_pool.h"
14 #include "base/timer/timer.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/printing/print_job_worker.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_service.h"
19 #include "printing/printed_document.h"
20 #include "printing/printed_page.h"
23 #include "chrome/browser/printing/pdf_to_emf_converter.h"
24 #include "printing/pdf_render_settings.h"
28 using base::TimeDelta;
32 // Helper function to ensure |owner| is valid until at least |callback| returns.
33 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
34 const base::Closure& callback) {
46 is_job_pending_(false),
49 // This is normally a UI message loop, but in unit tests, the message loop is
50 // of the 'default' type.
51 DCHECK(base::MessageLoopForUI::IsCurrent() ||
52 base::MessageLoop::current()->type() ==
53 base::MessageLoop::TYPE_DEFAULT);
56 PrintJob::~PrintJob() {
57 // The job should be finished (or at least canceled) when it is destroyed.
58 DCHECK(!is_job_pending_);
59 DCHECK(!is_canceling_);
60 DCHECK(!worker_ || !worker_->IsRunning());
61 DCHECK(RunsTasksOnCurrentThread());
64 void PrintJob::Initialize(PrintJobWorkerOwner* job,
65 PrintedPagesSource* source,
68 DCHECK(!worker_.get());
69 DCHECK(!is_job_pending_);
70 DCHECK(!is_canceling_);
71 DCHECK(!document_.get());
73 worker_.reset(job->DetachWorker(this));
74 settings_ = job->settings();
76 PrintedDocument* new_doc =
77 new PrintedDocument(settings_,
80 content::BrowserThread::GetBlockingPool());
81 new_doc->set_page_count(page_count);
82 UpdatePrintedDocument(new_doc);
84 // Don't forget to register to our own messages.
85 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
86 content::Source<PrintJob>(this));
89 void PrintJob::Observe(int type,
90 const content::NotificationSource& source,
91 const content::NotificationDetails& details) {
92 DCHECK(RunsTasksOnCurrentThread());
94 case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
95 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
104 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
105 PrintingContext::Result result) {
109 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
114 const PrintSettings& PrintJob::settings() const {
118 int PrintJob::cookie() const {
119 if (!document_.get())
120 // Always use an invalid cookie in this case.
122 return document_->cookie();
125 void PrintJob::StartPrinting() {
126 DCHECK(RunsTasksOnCurrentThread());
127 DCHECK(worker_->IsRunning());
128 DCHECK(!is_job_pending_);
129 if (!worker_->IsRunning() || is_job_pending_)
132 // Real work is done in PrintJobWorker::StartPrinting().
133 worker_->PostTask(FROM_HERE,
134 base::Bind(&HoldRefCallback,
135 make_scoped_refptr(this),
136 base::Bind(&PrintJobWorker::StartPrinting,
137 base::Unretained(worker_.get()),
138 base::RetainedRef(document_))));
139 // Set the flag right now.
140 is_job_pending_ = true;
143 scoped_refptr<JobEventDetails> details(
144 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
145 content::NotificationService::current()->Notify(
146 chrome::NOTIFICATION_PRINT_JOB_EVENT,
147 content::Source<PrintJob>(this),
148 content::Details<JobEventDetails>(details.get()));
151 void PrintJob::Stop() {
152 DCHECK(RunsTasksOnCurrentThread());
154 if (quit_factory_.HasWeakPtrs()) {
155 // In case we're running a nested message loop to wait for a job to finish,
156 // and we finished before the timeout, quit the nested loop right away.
158 quit_factory_.InvalidateWeakPtrs();
161 // Be sure to live long enough.
162 scoped_refptr<PrintJob> handle(this);
164 if (worker_->IsRunning()) {
165 ControlledWorkerShutdown();
167 // Flush the cached document.
168 UpdatePrintedDocument(NULL);
172 void PrintJob::Cancel() {
175 is_canceling_ = true;
177 // Be sure to live long enough.
178 scoped_refptr<PrintJob> handle(this);
180 DCHECK(RunsTasksOnCurrentThread());
181 if (worker_ && worker_->IsRunning()) {
182 // Call this right now so it renders the context invalid. Do not use
183 // InvokeLater since it would take too much time.
186 // Make sure a Cancel() is broadcast.
187 scoped_refptr<JobEventDetails> details(
188 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
189 content::NotificationService::current()->Notify(
190 chrome::NOTIFICATION_PRINT_JOB_EVENT,
191 content::Source<PrintJob>(this),
192 content::Details<JobEventDetails>(details.get()));
194 is_canceling_ = false;
197 bool PrintJob::FlushJob(base::TimeDelta timeout) {
198 // Make sure the object outlive this message loop.
199 scoped_refptr<PrintJob> handle(this);
201 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
202 FROM_HERE, base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()),
205 base::MessageLoop::ScopedNestableTaskAllower allow(
206 base::MessageLoop::current());
207 base::RunLoop().Run();
212 void PrintJob::DisconnectSource() {
215 document_->DisconnectSource();
218 bool PrintJob::is_job_pending() const {
219 return is_job_pending_;
222 PrintedDocument* PrintJob::document() const {
223 return document_.get();
228 class PrintJob::PdfToEmfState {
230 PdfToEmfState(const gfx::Size& page_size, const gfx::Rect& content_area)
233 pages_in_progress_(0),
234 page_size_(page_size),
235 content_area_(content_area),
236 converter_(PdfToEmfConverter::CreateDefault()) {}
238 void Start(const scoped_refptr<base::RefCountedMemory>& data,
239 const PdfRenderSettings& conversion_settings,
240 const PdfToEmfConverter::StartCallback& start_callback) {
241 converter_->Start(data, conversion_settings, start_callback);
245 const PdfToEmfConverter::GetPageCallback& get_page_callback) {
246 const int kMaxNumberOfTempFilesPerDocument = 3;
247 while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument &&
248 current_page_ < page_count_) {
249 ++pages_in_progress_;
250 converter_->GetPage(current_page_++, get_page_callback);
254 void OnPageProcessed(
255 const PdfToEmfConverter::GetPageCallback& get_page_callback) {
256 --pages_in_progress_;
257 GetMorePages(get_page_callback);
258 // Release converter if we don't need this any more.
259 if (!pages_in_progress_ && current_page_ >= page_count_)
263 void set_page_count(int page_count) { page_count_ = page_count; }
264 gfx::Size page_size() const { return page_size_; }
265 gfx::Rect content_area() const { return content_area_; }
270 int pages_in_progress_;
271 gfx::Size page_size_;
272 gfx::Rect content_area_;
273 std::unique_ptr<PdfToEmfConverter> converter_;
276 void PrintJob::StartPdfToEmfConversion(
277 const scoped_refptr<base::RefCountedMemory>& bytes,
278 const gfx::Size& page_size,
279 const gfx::Rect& content_area) {
280 DCHECK(!ptd_to_emf_state_.get());
281 ptd_to_emf_state_.reset(new PdfToEmfState(page_size, content_area));
282 const int kPrinterDpi = settings().dpi();
283 ptd_to_emf_state_->Start(
285 printing::PdfRenderSettings(content_area, kPrinterDpi, true),
286 base::Bind(&PrintJob::OnPdfToEmfStarted, this));
289 void PrintJob::OnPdfToEmfStarted(int page_count) {
290 if (page_count <= 0) {
291 ptd_to_emf_state_.reset();
295 ptd_to_emf_state_->set_page_count(page_count);
296 ptd_to_emf_state_->GetMorePages(
297 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
300 void PrintJob::OnPdfToEmfPageConverted(int page_number,
302 std::unique_ptr<MetafilePlayer> emf) {
303 DCHECK(ptd_to_emf_state_);
304 if (!document_.get() || !emf) {
305 ptd_to_emf_state_.reset();
310 // Update the rendered document. It will send notifications to the listener.
311 document_->SetPage(page_number,
314 ptd_to_emf_state_->page_size(),
315 ptd_to_emf_state_->content_area());
317 ptd_to_emf_state_->GetMorePages(
318 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
323 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
324 if (document_.get() == new_document)
327 document_ = new_document;
329 if (document_.get()) {
330 settings_ = document_->settings();
334 DCHECK(!is_job_pending_);
335 // Sync the document with the worker.
336 worker_->PostTask(FROM_HERE,
337 base::Bind(&HoldRefCallback,
338 make_scoped_refptr(this),
339 base::Bind(&PrintJobWorker::OnDocumentChanged,
340 base::Unretained(worker_.get()),
341 base::RetainedRef(document_))));
345 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
346 switch (event_details.type()) {
347 case JobEventDetails::FAILED: {
349 // No need to cancel since the worker already canceled itself.
353 case JobEventDetails::USER_INIT_DONE:
354 case JobEventDetails::DEFAULT_INIT_DONE:
355 case JobEventDetails::USER_INIT_CANCELED: {
356 DCHECK_EQ(event_details.document(), document_.get());
359 case JobEventDetails::NEW_DOC:
360 case JobEventDetails::NEW_PAGE:
361 case JobEventDetails::JOB_DONE:
362 case JobEventDetails::ALL_PAGES_REQUESTED: {
366 case JobEventDetails::DOC_DONE: {
367 // This will call Stop() and broadcast a JOB_DONE message.
368 base::ThreadTaskRunnerHandle::Get()->PostTask(
369 FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
372 case JobEventDetails::PAGE_DONE:
374 ptd_to_emf_state_->OnPageProcessed(
375 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
385 void PrintJob::OnDocumentDone() {
386 // Be sure to live long enough. The instance could be destroyed by the
387 // JOB_DONE broadcast.
388 scoped_refptr<PrintJob> handle(this);
390 // Stop the worker thread.
393 scoped_refptr<JobEventDetails> details(
394 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
395 content::NotificationService::current()->Notify(
396 chrome::NOTIFICATION_PRINT_JOB_EVENT,
397 content::Source<PrintJob>(this),
398 content::Details<JobEventDetails>(details.get()));
401 void PrintJob::ControlledWorkerShutdown() {
402 DCHECK(RunsTasksOnCurrentThread());
404 // The deadlock this code works around is specific to window messaging on
405 // Windows, so we aren't likely to need it on any other platforms.
407 // We could easily get into a deadlock case if worker_->Stop() is used; the
408 // printer driver created a window as a child of the browser window. By
409 // canceling the job, the printer driver initiated dialog box is destroyed,
410 // which sends a blocking message to its parent window. If the browser window
411 // thread is not processing messages, a deadlock occurs.
413 // This function ensures that the dialog box will be destroyed in a timely
414 // manner by the mere fact that the thread will terminate. So the potential
415 // deadlock is eliminated.
418 // Delay shutdown until the worker terminates. We want this code path
419 // to wait on the thread to quit before continuing.
420 if (worker_->IsRunning()) {
421 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
423 base::Bind(&PrintJob::ControlledWorkerShutdown, this),
424 base::TimeDelta::FromMilliseconds(100));
430 // Now make sure the thread object is cleaned up. Do this on a worker
431 // thread because it may block.
432 base::WorkerPool::PostTaskAndReply(
434 base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
435 base::Bind(&PrintJob::HoldUntilStopIsCalled, this),
438 is_job_pending_ = false;
439 registrar_.RemoveAll();
440 UpdatePrintedDocument(NULL);
443 void PrintJob::HoldUntilStopIsCalled() {
446 void PrintJob::Quit() {
447 base::MessageLoop::current()->QuitWhenIdle();
450 // Takes settings_ ownership and will be deleted in the receiving thread.
451 JobEventDetails::JobEventDetails(Type type,
452 PrintedDocument* document,
454 : document_(document),
459 JobEventDetails::~JobEventDetails() {
462 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
464 PrintedPage* JobEventDetails::page() const { return page_.get(); }
466 } // namespace printing