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/threading/thread_restrictions.h"
11 #include "base/threading/worker_pool.h"
12 #include "base/timer/timer.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/printing/print_job_worker.h"
15 #include "content/public/browser/notification_service.h"
16 #include "printing/printed_document.h"
17 #include "printing/printed_page.h"
19 using base::TimeDelta
;
23 // Helper function to ensure |owner| is valid until at least |callback| returns.
24 void HoldRefCallback(const scoped_refptr
<printing::PrintJobWorkerOwner
>& owner
,
25 const base::Closure
& callback
) {
34 : ui_message_loop_(base::MessageLoop::current()),
38 is_job_pending_(false),
41 DCHECK(ui_message_loop_
);
42 // This is normally a UI message loop, but in unit tests, the message loop is
43 // of the 'default' type.
44 DCHECK(base::MessageLoopForUI::IsCurrent() ||
45 ui_message_loop_
->type() == base::MessageLoop::TYPE_DEFAULT
);
46 ui_message_loop_
->AddDestructionObserver(this);
49 PrintJob::~PrintJob() {
50 ui_message_loop_
->RemoveDestructionObserver(this);
51 // The job should be finished (or at least canceled) when it is destroyed.
52 DCHECK(!is_job_pending_
);
53 DCHECK(!is_canceling_
);
55 DCHECK(worker_
->message_loop() == NULL
);
56 DCHECK_EQ(ui_message_loop_
, base::MessageLoop::current());
59 void PrintJob::Initialize(PrintJobWorkerOwner
* job
,
60 PrintedPagesSource
* source
,
63 DCHECK(!worker_
.get());
64 DCHECK(!is_job_pending_
);
65 DCHECK(!is_canceling_
);
66 DCHECK(!document_
.get());
68 worker_
.reset(job
->DetachWorker(this));
69 settings_
= job
->settings();
71 PrintedDocument
* new_doc
=
72 new PrintedDocument(settings_
, source_
, job
->cookie());
73 new_doc
->set_page_count(page_count
);
74 UpdatePrintedDocument(new_doc
);
76 // Don't forget to register to our own messages.
77 registrar_
.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT
,
78 content::Source
<PrintJob
>(this));
81 void PrintJob::Observe(int type
,
82 const content::NotificationSource
& source
,
83 const content::NotificationDetails
& details
) {
84 DCHECK_EQ(ui_message_loop_
, base::MessageLoop::current());
86 case chrome::NOTIFICATION_PRINT_JOB_EVENT
: {
87 OnNotifyPrintJobEvent(*content::Details
<JobEventDetails
>(details
).ptr());
96 void PrintJob::GetSettingsDone(const PrintSettings
& new_settings
,
97 PrintingContext::Result result
) {
101 PrintJobWorker
* PrintJob::DetachWorker(PrintJobWorkerOwner
* new_owner
) {
106 base::MessageLoop
* PrintJob::message_loop() {
107 return ui_message_loop_
;
110 const PrintSettings
& PrintJob::settings() const {
114 int PrintJob::cookie() const {
115 if (!document_
.get())
116 // Always use an invalid cookie in this case.
118 return document_
->cookie();
121 void PrintJob::WillDestroyCurrentMessageLoop() {
125 void PrintJob::StartPrinting() {
126 DCHECK_EQ(ui_message_loop_
, base::MessageLoop::current());
127 DCHECK(worker_
->message_loop());
128 DCHECK(!is_job_pending_
);
129 if (!worker_
->message_loop() || is_job_pending_
)
132 // Real work is done in PrintJobWorker::StartPrinting().
133 worker_
->message_loop()->PostTask(
135 base::Bind(&HoldRefCallback
, make_scoped_refptr(this),
136 base::Bind(&PrintJobWorker::StartPrinting
,
137 base::Unretained(worker_
.get()), document_
)));
138 // Set the flag right now.
139 is_job_pending_
= true;
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_EQ(ui_message_loop_
, base::MessageLoop::current());
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.
157 quit_factory_
.InvalidateWeakPtrs();
160 // Be sure to live long enough.
161 scoped_refptr
<PrintJob
> handle(this);
163 if (worker_
->message_loop()) {
164 ControlledWorkerShutdown();
166 // Flush the cached document.
167 UpdatePrintedDocument(NULL
);
171 void PrintJob::Cancel() {
174 is_canceling_
= true;
176 // Be sure to live long enough.
177 scoped_refptr
<PrintJob
> handle(this);
179 DCHECK_EQ(ui_message_loop_
, base::MessageLoop::current());
180 base::MessageLoop
* worker_loop
=
181 worker_
.get() ? worker_
->message_loop() : NULL
;
183 // Call this right now so it renders the context invalid. Do not use
184 // InvokeLater since it would take too much time.
187 // Make sure a Cancel() is broadcast.
188 scoped_refptr
<JobEventDetails
> details(
189 new JobEventDetails(JobEventDetails::FAILED
, NULL
, NULL
));
190 content::NotificationService::current()->Notify(
191 chrome::NOTIFICATION_PRINT_JOB_EVENT
,
192 content::Source
<PrintJob
>(this),
193 content::Details
<JobEventDetails
>(details
.get()));
195 is_canceling_
= false;
198 bool PrintJob::FlushJob(base::TimeDelta timeout
) {
199 // Make sure the object outlive this message loop.
200 scoped_refptr
<PrintJob
> handle(this);
202 base::MessageLoop::current()->PostDelayedTask(FROM_HERE
,
203 base::Bind(&PrintJob::Quit
, quit_factory_
.GetWeakPtr()), timeout
);
205 base::MessageLoop::ScopedNestableTaskAllower
allow(
206 base::MessageLoop::current());
207 base::MessageLoop::current()->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();
226 void PrintJob::UpdatePrintedDocument(PrintedDocument
* new_document
) {
227 if (document_
.get() == new_document
)
230 document_
= new_document
;
232 if (document_
.get()) {
233 settings_
= document_
->settings();
236 if (worker_
.get() && worker_
->message_loop()) {
237 DCHECK(!is_job_pending_
);
238 // Sync the document with the worker.
239 worker_
->message_loop()->PostTask(
241 base::Bind(&HoldRefCallback
, make_scoped_refptr(this),
242 base::Bind(&PrintJobWorker::OnDocumentChanged
,
243 base::Unretained(worker_
.get()), document_
)));
247 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails
& event_details
) {
248 switch (event_details
.type()) {
249 case JobEventDetails::FAILED
: {
251 // No need to cancel since the worker already canceled itself.
255 case JobEventDetails::USER_INIT_DONE
:
256 case JobEventDetails::DEFAULT_INIT_DONE
:
257 case JobEventDetails::USER_INIT_CANCELED
: {
258 DCHECK_EQ(event_details
.document(), document_
.get());
261 case JobEventDetails::NEW_DOC
:
262 case JobEventDetails::NEW_PAGE
:
263 case JobEventDetails::PAGE_DONE
:
264 case JobEventDetails::JOB_DONE
:
265 case JobEventDetails::ALL_PAGES_REQUESTED
: {
269 case JobEventDetails::DOC_DONE
: {
270 // This will call Stop() and broadcast a JOB_DONE message.
271 base::MessageLoop::current()->PostTask(
272 FROM_HERE
, base::Bind(&PrintJob::OnDocumentDone
, this));
282 void PrintJob::OnDocumentDone() {
283 // Be sure to live long enough. The instance could be destroyed by the
284 // JOB_DONE broadcast.
285 scoped_refptr
<PrintJob
> handle(this);
287 // Stop the worker thread.
290 scoped_refptr
<JobEventDetails
> details(
291 new JobEventDetails(JobEventDetails::JOB_DONE
, document_
.get(), NULL
));
292 content::NotificationService::current()->Notify(
293 chrome::NOTIFICATION_PRINT_JOB_EVENT
,
294 content::Source
<PrintJob
>(this),
295 content::Details
<JobEventDetails
>(details
.get()));
298 void PrintJob::ControlledWorkerShutdown() {
299 DCHECK_EQ(ui_message_loop_
, base::MessageLoop::current());
301 // The deadlock this code works around is specific to window messaging on
302 // Windows, so we aren't likely to need it on any other platforms.
304 // We could easily get into a deadlock case if worker_->Stop() is used; the
305 // printer driver created a window as a child of the browser window. By
306 // canceling the job, the printer driver initiated dialog box is destroyed,
307 // which sends a blocking message to its parent window. If the browser window
308 // thread is not processing messages, a deadlock occurs.
310 // This function ensures that the dialog box will be destroyed in a timely
311 // manner by the mere fact that the thread will terminate. So the potential
312 // deadlock is eliminated.
315 // Delay shutdown until the worker terminates. We want this code path
316 // to wait on the thread to quit before continuing.
317 if (worker_
->IsRunning()) {
318 base::MessageLoop::current()->PostDelayedTask(
320 base::Bind(&PrintJob::ControlledWorkerShutdown
, this),
321 base::TimeDelta::FromMilliseconds(100));
327 // Now make sure the thread object is cleaned up. Do this on a worker
328 // thread because it may block.
329 base::WorkerPool::PostTaskAndReply(
331 base::Bind(&PrintJobWorker::Stop
, base::Unretained(worker_
.get())),
332 base::Bind(&PrintJob::HoldUntilStopIsCalled
, this),
335 is_job_pending_
= false;
336 registrar_
.RemoveAll();
337 UpdatePrintedDocument(NULL
);
340 void PrintJob::HoldUntilStopIsCalled() {
343 void PrintJob::Quit() {
344 base::MessageLoop::current()->Quit();
347 // Takes settings_ ownership and will be deleted in the receiving thread.
348 JobEventDetails::JobEventDetails(Type type
,
349 PrintedDocument
* document
,
351 : document_(document
),
356 JobEventDetails::~JobEventDetails() {
359 PrintedDocument
* JobEventDetails::document() const { return document_
.get(); }
361 PrintedPage
* JobEventDetails::page() const { return page_
.get(); }
363 } // namespace printing