Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / printing / print_job.cc
blob66473c275dd92ee67625d5d81269966f26e29ac9
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"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/location.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/single_thread_task_runner.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "base/threading/thread_restrictions.h"
14 #include "base/threading/worker_pool.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"
22 #if defined(OS_WIN)
23 #include "chrome/browser/printing/pdf_to_emf_converter.h"
24 #include "printing/pdf_render_settings.h"
25 #endif
27 using base::TimeDelta;
29 namespace {
31 // Helper function to ensure |owner| is valid until at least |callback| returns.
32 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
33 const base::Closure& callback) {
34 callback.Run();
37 } // namespace
39 namespace printing {
41 PrintJob::PrintJob()
42 : source_(NULL),
43 worker_(),
44 settings_(),
45 is_job_pending_(false),
46 is_canceling_(false),
47 quit_factory_(this) {
48 // This is normally a UI message loop, but in unit tests, the message loop is
49 // of the 'default' type.
50 DCHECK(base::MessageLoopForUI::IsCurrent() ||
51 base::MessageLoop::current()->type() ==
52 base::MessageLoop::TYPE_DEFAULT);
55 PrintJob::~PrintJob() {
56 // The job should be finished (or at least canceled) when it is destroyed.
57 DCHECK(!is_job_pending_);
58 DCHECK(!is_canceling_);
59 DCHECK(!worker_ || !worker_->IsRunning());
60 DCHECK(RunsTasksOnCurrentThread());
63 void PrintJob::Initialize(PrintJobWorkerOwner* job,
64 PrintedPagesSource* source,
65 int page_count) {
66 DCHECK(!source_);
67 DCHECK(!worker_.get());
68 DCHECK(!is_job_pending_);
69 DCHECK(!is_canceling_);
70 DCHECK(!document_.get());
71 source_ = source;
72 worker_.reset(job->DetachWorker(this));
73 settings_ = job->settings();
75 PrintedDocument* new_doc =
76 new PrintedDocument(settings_,
77 source_,
78 job->cookie(),
79 content::BrowserThread::GetBlockingPool());
80 new_doc->set_page_count(page_count);
81 UpdatePrintedDocument(new_doc);
83 // Don't forget to register to our own messages.
84 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
85 content::Source<PrintJob>(this));
88 void PrintJob::Observe(int type,
89 const content::NotificationSource& source,
90 const content::NotificationDetails& details) {
91 DCHECK(RunsTasksOnCurrentThread());
92 switch (type) {
93 case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
94 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
95 break;
97 default: {
98 break;
103 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
104 PrintingContext::Result result) {
105 NOTREACHED();
108 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
109 NOTREACHED();
110 return NULL;
113 const PrintSettings& PrintJob::settings() const {
114 return settings_;
117 int PrintJob::cookie() const {
118 if (!document_.get())
119 // Always use an invalid cookie in this case.
120 return 0;
121 return document_->cookie();
124 void PrintJob::StartPrinting() {
125 DCHECK(RunsTasksOnCurrentThread());
126 DCHECK(worker_->IsRunning());
127 DCHECK(!is_job_pending_);
128 if (!worker_->IsRunning() || is_job_pending_)
129 return;
131 // Real work is done in PrintJobWorker::StartPrinting().
132 worker_->PostTask(FROM_HERE,
133 base::Bind(&HoldRefCallback,
134 make_scoped_refptr(this),
135 base::Bind(&PrintJobWorker::StartPrinting,
136 base::Unretained(worker_.get()),
137 document_)));
138 // Set the flag right now.
139 is_job_pending_ = true;
141 // Tell everyone!
142 scoped_refptr<JobEventDetails> details(
143 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
144 content::NotificationService::current()->Notify(
145 chrome::NOTIFICATION_PRINT_JOB_EVENT,
146 content::Source<PrintJob>(this),
147 content::Details<JobEventDetails>(details.get()));
150 void PrintJob::Stop() {
151 DCHECK(RunsTasksOnCurrentThread());
153 if (quit_factory_.HasWeakPtrs()) {
154 // In case we're running a nested message loop to wait for a job to finish,
155 // and we finished before the timeout, quit the nested loop right away.
156 Quit();
157 quit_factory_.InvalidateWeakPtrs();
160 // Be sure to live long enough.
161 scoped_refptr<PrintJob> handle(this);
163 if (worker_->IsRunning()) {
164 ControlledWorkerShutdown();
165 } else {
166 // Flush the cached document.
167 UpdatePrintedDocument(NULL);
171 void PrintJob::Cancel() {
172 if (is_canceling_)
173 return;
174 is_canceling_ = true;
176 // Be sure to live long enough.
177 scoped_refptr<PrintJob> handle(this);
179 DCHECK(RunsTasksOnCurrentThread());
180 if (worker_ && worker_->IsRunning()) {
181 // Call this right now so it renders the context invalid. Do not use
182 // InvokeLater since it would take too much time.
183 worker_->Cancel();
185 // Make sure a Cancel() is broadcast.
186 scoped_refptr<JobEventDetails> details(
187 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
188 content::NotificationService::current()->Notify(
189 chrome::NOTIFICATION_PRINT_JOB_EVENT,
190 content::Source<PrintJob>(this),
191 content::Details<JobEventDetails>(details.get()));
192 Stop();
193 is_canceling_ = false;
196 bool PrintJob::FlushJob(base::TimeDelta timeout) {
197 // Make sure the object outlive this message loop.
198 scoped_refptr<PrintJob> handle(this);
200 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
201 FROM_HERE, base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()),
202 timeout);
204 base::MessageLoop::ScopedNestableTaskAllower allow(
205 base::MessageLoop::current());
206 base::MessageLoop::current()->Run();
208 return true;
211 void PrintJob::DisconnectSource() {
212 source_ = NULL;
213 if (document_.get())
214 document_->DisconnectSource();
217 bool PrintJob::is_job_pending() const {
218 return is_job_pending_;
221 PrintedDocument* PrintJob::document() const {
222 return document_.get();
225 #if defined(OS_WIN)
227 class PrintJob::PdfToEmfState {
228 public:
229 PdfToEmfState(const gfx::Size& page_size, const gfx::Rect& content_area)
230 : page_count_(0),
231 current_page_(0),
232 pages_in_progress_(0),
233 page_size_(page_size),
234 content_area_(content_area),
235 converter_(PdfToEmfConverter::CreateDefault()) {}
237 void Start(const scoped_refptr<base::RefCountedMemory>& data,
238 const PdfRenderSettings& conversion_settings,
239 const PdfToEmfConverter::StartCallback& start_callback) {
240 converter_->Start(data, conversion_settings, start_callback);
243 void GetMorePages(
244 const PdfToEmfConverter::GetPageCallback& get_page_callback) {
245 const int kMaxNumberOfTempFilesPerDocument = 3;
246 while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument &&
247 current_page_ < page_count_) {
248 ++pages_in_progress_;
249 converter_->GetPage(current_page_++, get_page_callback);
253 void OnPageProcessed(
254 const PdfToEmfConverter::GetPageCallback& get_page_callback) {
255 --pages_in_progress_;
256 GetMorePages(get_page_callback);
257 // Release converter if we don't need this any more.
258 if (!pages_in_progress_ && current_page_ >= page_count_)
259 converter_.reset();
262 void set_page_count(int page_count) { page_count_ = page_count; }
263 gfx::Size page_size() const { return page_size_; }
264 gfx::Rect content_area() const { return content_area_; }
266 private:
267 int page_count_;
268 int current_page_;
269 int pages_in_progress_;
270 gfx::Size page_size_;
271 gfx::Rect content_area_;
272 scoped_ptr<PdfToEmfConverter> converter_;
275 void PrintJob::StartPdfToEmfConversion(
276 const scoped_refptr<base::RefCountedMemory>& bytes,
277 const gfx::Size& page_size,
278 const gfx::Rect& content_area) {
279 DCHECK(!ptd_to_emf_state_.get());
280 ptd_to_emf_state_.reset(new PdfToEmfState(page_size, content_area));
281 const int kPrinterDpi = settings().dpi();
282 ptd_to_emf_state_->Start(
283 bytes,
284 printing::PdfRenderSettings(content_area, kPrinterDpi, true),
285 base::Bind(&PrintJob::OnPdfToEmfStarted, this));
288 void PrintJob::OnPdfToEmfStarted(int page_count) {
289 if (page_count <= 0) {
290 ptd_to_emf_state_.reset();
291 Cancel();
292 return;
294 ptd_to_emf_state_->set_page_count(page_count);
295 ptd_to_emf_state_->GetMorePages(
296 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
299 void PrintJob::OnPdfToEmfPageConverted(int page_number,
300 float scale_factor,
301 scoped_ptr<MetafilePlayer> emf) {
302 DCHECK(ptd_to_emf_state_);
303 if (!document_.get() || !emf) {
304 ptd_to_emf_state_.reset();
305 Cancel();
306 return;
309 // Update the rendered document. It will send notifications to the listener.
310 document_->SetPage(page_number,
311 emf.Pass(),
312 scale_factor,
313 ptd_to_emf_state_->page_size(),
314 ptd_to_emf_state_->content_area());
316 ptd_to_emf_state_->GetMorePages(
317 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
320 #endif // OS_WIN
322 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
323 if (document_.get() == new_document)
324 return;
326 document_ = new_document;
328 if (document_.get()) {
329 settings_ = document_->settings();
332 if (worker_) {
333 DCHECK(!is_job_pending_);
334 // Sync the document with the worker.
335 worker_->PostTask(FROM_HERE,
336 base::Bind(&HoldRefCallback,
337 make_scoped_refptr(this),
338 base::Bind(&PrintJobWorker::OnDocumentChanged,
339 base::Unretained(worker_.get()),
340 document_)));
344 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
345 switch (event_details.type()) {
346 case JobEventDetails::FAILED: {
347 settings_.Clear();
348 // No need to cancel since the worker already canceled itself.
349 Stop();
350 break;
352 case JobEventDetails::USER_INIT_DONE:
353 case JobEventDetails::DEFAULT_INIT_DONE:
354 case JobEventDetails::USER_INIT_CANCELED: {
355 DCHECK_EQ(event_details.document(), document_.get());
356 break;
358 case JobEventDetails::NEW_DOC:
359 case JobEventDetails::NEW_PAGE:
360 case JobEventDetails::JOB_DONE:
361 case JobEventDetails::ALL_PAGES_REQUESTED: {
362 // Don't care.
363 break;
365 case JobEventDetails::DOC_DONE: {
366 // This will call Stop() and broadcast a JOB_DONE message.
367 base::ThreadTaskRunnerHandle::Get()->PostTask(
368 FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
369 break;
371 case JobEventDetails::PAGE_DONE:
372 #if defined(OS_WIN)
373 ptd_to_emf_state_->OnPageProcessed(
374 base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
375 #endif // OS_WIN
376 break;
377 default: {
378 NOTREACHED();
379 break;
384 void PrintJob::OnDocumentDone() {
385 // Be sure to live long enough. The instance could be destroyed by the
386 // JOB_DONE broadcast.
387 scoped_refptr<PrintJob> handle(this);
389 // Stop the worker thread.
390 Stop();
392 scoped_refptr<JobEventDetails> details(
393 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
394 content::NotificationService::current()->Notify(
395 chrome::NOTIFICATION_PRINT_JOB_EVENT,
396 content::Source<PrintJob>(this),
397 content::Details<JobEventDetails>(details.get()));
400 void PrintJob::ControlledWorkerShutdown() {
401 DCHECK(RunsTasksOnCurrentThread());
403 // The deadlock this code works around is specific to window messaging on
404 // Windows, so we aren't likely to need it on any other platforms.
405 #if defined(OS_WIN)
406 // We could easily get into a deadlock case if worker_->Stop() is used; the
407 // printer driver created a window as a child of the browser window. By
408 // canceling the job, the printer driver initiated dialog box is destroyed,
409 // which sends a blocking message to its parent window. If the browser window
410 // thread is not processing messages, a deadlock occurs.
412 // This function ensures that the dialog box will be destroyed in a timely
413 // manner by the mere fact that the thread will terminate. So the potential
414 // deadlock is eliminated.
415 worker_->StopSoon();
417 // Delay shutdown until the worker terminates. We want this code path
418 // to wait on the thread to quit before continuing.
419 if (worker_->IsRunning()) {
420 base::MessageLoop::current()->PostDelayedTask(
421 FROM_HERE,
422 base::Bind(&PrintJob::ControlledWorkerShutdown, this),
423 base::TimeDelta::FromMilliseconds(100));
424 return;
426 #endif
429 // Now make sure the thread object is cleaned up. Do this on a worker
430 // thread because it may block.
431 base::WorkerPool::PostTaskAndReply(
432 FROM_HERE,
433 base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
434 base::Bind(&PrintJob::HoldUntilStopIsCalled, this),
435 false);
437 is_job_pending_ = false;
438 registrar_.RemoveAll();
439 UpdatePrintedDocument(NULL);
442 void PrintJob::HoldUntilStopIsCalled() {
445 void PrintJob::Quit() {
446 base::MessageLoop::current()->Quit();
449 // Takes settings_ ownership and will be deleted in the receiving thread.
450 JobEventDetails::JobEventDetails(Type type,
451 PrintedDocument* document,
452 PrintedPage* page)
453 : document_(document),
454 page_(page),
455 type_(type) {
458 JobEventDetails::~JobEventDetails() {
461 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
463 PrintedPage* JobEventDetails::page() const { return page_.get(); }
465 } // namespace printing