1 // Copyright (c) 2011 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/print_job.h"
6
7 #include "base/message_loop.h"
8 #include "base/threading/thread_restrictions.h"
9 #include "base/timer.h"
10 #include "chrome/browser/printing/print_job_worker.h"
11 #include "content/common/notification_service.h"
12 #include "printing/printed_document.h"
13 #include "printing/printed_page.h"
14
15 using base::TimeDelta;
16
17 namespace printing {
18
PrintJob()19 PrintJob::PrintJob()
20 : ui_message_loop_(MessageLoop::current()),
21 source_(NULL),
22 worker_(),
23 settings_(),
24 is_job_pending_(false),
25 is_canceling_(false) {
26 DCHECK(ui_message_loop_);
27 // This is normally a UI message loop, but in unit tests, the message loop is
28 // of the 'default' type.
29 DCHECK(ui_message_loop_->type() == MessageLoop::TYPE_UI ||
30 ui_message_loop_->type() == MessageLoop::TYPE_DEFAULT);
31 ui_message_loop_->AddDestructionObserver(this);
32 }
33
~PrintJob()34 PrintJob::~PrintJob() {
35 ui_message_loop_->RemoveDestructionObserver(this);
36 // The job should be finished (or at least canceled) when it is destroyed.
37 DCHECK(!is_job_pending_);
38 DCHECK(!is_canceling_);
39 if (worker_.get())
40 DCHECK(worker_->message_loop() == NULL);
41 DCHECK_EQ(ui_message_loop_, MessageLoop::current());
42 }
43
Initialize(PrintJobWorkerOwner * job,PrintedPagesSource * source,int page_count)44 void PrintJob::Initialize(PrintJobWorkerOwner* job,
45 PrintedPagesSource* source,
46 int page_count) {
47 DCHECK(!source_);
48 DCHECK(!worker_.get());
49 DCHECK(!is_job_pending_);
50 DCHECK(!is_canceling_);
51 DCHECK(!document_.get());
52 source_ = source;
53 worker_.reset(job->DetachWorker(this));
54 settings_ = job->settings();
55
56 PrintedDocument* new_doc =
57 new PrintedDocument(settings_, source_, job->cookie());
58 new_doc->set_page_count(page_count);
59 UpdatePrintedDocument(new_doc);
60
61 // Don't forget to register to our own messages.
62 registrar_.Add(this, NotificationType::PRINT_JOB_EVENT,
63 Source<PrintJob>(this));
64 }
65
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)66 void PrintJob::Observe(NotificationType type,
67 const NotificationSource& source,
68 const NotificationDetails& details) {
69 DCHECK_EQ(ui_message_loop_, MessageLoop::current());
70 switch (type.value) {
71 case NotificationType::PRINT_JOB_EVENT: {
72 OnNotifyPrintJobEvent(*Details<JobEventDetails>(details).ptr());
73 break;
74 }
75 default: {
76 break;
77 }
78 }
79 }
80
GetSettingsDone(const PrintSettings & new_settings,PrintingContext::Result result)81 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
82 PrintingContext::Result result) {
83 NOTREACHED();
84 }
85
DetachWorker(PrintJobWorkerOwner * new_owner)86 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
87 NOTREACHED();
88 return NULL;
89 }
90
message_loop()91 MessageLoop* PrintJob::message_loop() {
92 return ui_message_loop_;
93 }
94
settings() const95 const PrintSettings& PrintJob::settings() const {
96 return settings_;
97 }
98
cookie() const99 int PrintJob::cookie() const {
100 if (!document_.get())
101 // Always use an invalid cookie in this case.
102 return 0;
103 return document_->cookie();
104 }
105
WillDestroyCurrentMessageLoop()106 void PrintJob::WillDestroyCurrentMessageLoop() {
107 NOTREACHED();
108 }
109
StartPrinting()110 void PrintJob::StartPrinting() {
111 DCHECK_EQ(ui_message_loop_, MessageLoop::current());
112 DCHECK(worker_->message_loop());
113 DCHECK(!is_job_pending_);
114 if (!worker_->message_loop() || is_job_pending_)
115 return;
116
117 // Real work is done in PrintJobWorker::StartPrinting().
118 worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
119 worker_.get(), &PrintJobWorker::StartPrinting, document_));
120 // Set the flag right now.
121 is_job_pending_ = true;
122
123 // Tell everyone!
124 scoped_refptr<JobEventDetails> details(
125 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
126 NotificationService::current()->Notify(
127 NotificationType::PRINT_JOB_EVENT,
128 Source<PrintJob>(this),
129 Details<JobEventDetails>(details.get()));
130 }
131
Stop()132 void PrintJob::Stop() {
133 DCHECK_EQ(ui_message_loop_, MessageLoop::current());
134
135 // Be sure to live long enough.
136 scoped_refptr<PrintJob> handle(this);
137
138 MessageLoop* worker_loop = worker_->message_loop();
139 if (worker_loop) {
140 ControlledWorkerShutdown();
141
142 is_job_pending_ = false;
143 registrar_.Remove(this, NotificationType::PRINT_JOB_EVENT,
144 Source<PrintJob>(this));
145 }
146 // Flush the cached document.
147 UpdatePrintedDocument(NULL);
148 }
149
Cancel()150 void PrintJob::Cancel() {
151 if (is_canceling_)
152 return;
153 is_canceling_ = true;
154
155 // Be sure to live long enough.
156 scoped_refptr<PrintJob> handle(this);
157
158 DCHECK_EQ(ui_message_loop_, MessageLoop::current());
159 MessageLoop* worker_loop = worker_.get() ? worker_->message_loop() : NULL;
160 if (worker_loop) {
161 // Call this right now so it renders the context invalid. Do not use
162 // InvokeLater since it would take too much time.
163 worker_->Cancel();
164 }
165 // Make sure a Cancel() is broadcast.
166 scoped_refptr<JobEventDetails> details(
167 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
168 NotificationService::current()->Notify(
169 NotificationType::PRINT_JOB_EVENT,
170 Source<PrintJob>(this),
171 Details<JobEventDetails>(details.get()));
172 Stop();
173 is_canceling_ = false;
174 }
175
FlushJob(int timeout_ms)176 bool PrintJob::FlushJob(int timeout_ms) {
177 // Make sure the object outlive this message loop.
178 scoped_refptr<PrintJob> handle(this);
179
180 // Stop() will eventually be called, which will get out of the inner message
181 // loop. But, don't take it for granted and set a timer in case something goes
182 // wrong.
183 base::OneShotTimer<MessageLoop> quit_task;
184 if (timeout_ms) {
185 quit_task.Start(TimeDelta::FromMilliseconds(timeout_ms),
186 MessageLoop::current(), &MessageLoop::Quit);
187 }
188
189 bool old_state = MessageLoop::current()->NestableTasksAllowed();
190 MessageLoop::current()->SetNestableTasksAllowed(true);
191 MessageLoop::current()->Run();
192 // Restore task state.
193 MessageLoop::current()->SetNestableTasksAllowed(old_state);
194
195 return true;
196 }
197
DisconnectSource()198 void PrintJob::DisconnectSource() {
199 source_ = NULL;
200 if (document_.get())
201 document_->DisconnectSource();
202 }
203
is_job_pending() const204 bool PrintJob::is_job_pending() const {
205 return is_job_pending_;
206 }
207
document() const208 PrintedDocument* PrintJob::document() const {
209 return document_.get();
210 }
211
UpdatePrintedDocument(PrintedDocument * new_document)212 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
213 if (document_.get() == new_document)
214 return;
215
216 document_ = new_document;
217
218 if (document_.get()) {
219 settings_ = document_->settings();
220 }
221
222 if (worker_.get() && worker_->message_loop()) {
223 DCHECK(!is_job_pending_);
224 // Sync the document with the worker.
225 worker_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
226 worker_.get(), &PrintJobWorker::OnDocumentChanged, document_));
227 }
228 }
229
OnNotifyPrintJobEvent(const JobEventDetails & event_details)230 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
231 switch (event_details.type()) {
232 case JobEventDetails::FAILED: {
233 settings_.Clear();
234 // No need to cancel since the worker already canceled itself.
235 Stop();
236 break;
237 }
238 case JobEventDetails::USER_INIT_DONE:
239 case JobEventDetails::DEFAULT_INIT_DONE:
240 case JobEventDetails::USER_INIT_CANCELED: {
241 DCHECK_EQ(event_details.document(), document_.get());
242 break;
243 }
244 case JobEventDetails::NEW_DOC:
245 case JobEventDetails::NEW_PAGE:
246 case JobEventDetails::PAGE_DONE:
247 case JobEventDetails::JOB_DONE:
248 case JobEventDetails::ALL_PAGES_REQUESTED: {
249 // Don't care.
250 break;
251 }
252 case JobEventDetails::DOC_DONE: {
253 // This will call Stop() and broadcast a JOB_DONE message.
254 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
255 this, &PrintJob::OnDocumentDone));
256 break;
257 }
258 default: {
259 NOTREACHED();
260 break;
261 }
262 }
263 }
264
OnDocumentDone()265 void PrintJob::OnDocumentDone() {
266 // Be sure to live long enough. The instance could be destroyed by the
267 // JOB_DONE broadcast.
268 scoped_refptr<PrintJob> handle(this);
269
270 // Stop the worker thread.
271 Stop();
272
273 scoped_refptr<JobEventDetails> details(
274 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
275 NotificationService::current()->Notify(
276 NotificationType::PRINT_JOB_EVENT,
277 Source<PrintJob>(this),
278 Details<JobEventDetails>(details.get()));
279 }
280
ControlledWorkerShutdown()281 void PrintJob::ControlledWorkerShutdown() {
282 DCHECK_EQ(ui_message_loop_, MessageLoop::current());
283
284 // The deadlock this code works around is specific to window messaging on
285 // Windows, so we aren't likely to need it on any other platforms.
286 #if defined(OS_WIN)
287 // We could easily get into a deadlock case if worker_->Stop() is used; the
288 // printer driver created a window as a child of the browser window. By
289 // canceling the job, the printer driver initiated dialog box is destroyed,
290 // which sends a blocking message to its parent window. If the browser window
291 // thread is not processing messages, a deadlock occurs.
292 //
293 // This function ensures that the dialog box will be destroyed in a timely
294 // manner by the mere fact that the thread will terminate. So the potential
295 // deadlock is eliminated.
296 worker_->StopSoon();
297
298 // Run a tight message loop until the worker terminates. It may seems like a
299 // hack but I see no other way to get it to work flawlessly. The issues here
300 // are:
301 // - We don't want to run tasks while the thread is quitting.
302 // - We want this code path to wait on the thread to quit before continuing.
303 MSG msg;
304 HANDLE thread_handle = worker_->thread_handle();
305 for (; thread_handle;) {
306 // Note that we don't do any kind of message priorization since we don't
307 // execute any pending task or timer.
308 DWORD result = MsgWaitForMultipleObjects(1, &thread_handle,
309 FALSE, INFINITE, QS_ALLINPUT);
310 if (result == WAIT_OBJECT_0 + 1) {
311 while (PeekMessage(&msg, NULL, 0, 0, TRUE) > 0) {
312 TranslateMessage(&msg);
313 DispatchMessage(&msg);
314 }
315 // Continue looping.
316 } else if (result == WAIT_OBJECT_0) {
317 // The thread quit.
318 break;
319 } else {
320 // An error occured. Assume the thread quit.
321 NOTREACHED();
322 break;
323 }
324 }
325 #endif
326
327 // Temporarily allow it until we fix
328 // http://code.google.com/p/chromium/issues/detail?id=67044
329 base::ThreadRestrictions::ScopedAllowIO allow_io;
330
331 // Now make sure the thread object is cleaned up.
332 worker_->Stop();
333 }
334
335 // Takes settings_ ownership and will be deleted in the receiving thread.
JobEventDetails(Type type,PrintedDocument * document,PrintedPage * page)336 JobEventDetails::JobEventDetails(Type type,
337 PrintedDocument* document,
338 PrintedPage* page)
339 : document_(document),
340 page_(page),
341 type_(type) {
342 }
343
~JobEventDetails()344 JobEventDetails::~JobEventDetails() {
345 }
346
document() const347 PrintedDocument* JobEventDetails::document() const {
348 return document_;
349 }
350
page() const351 PrintedPage* JobEventDetails::page() const {
352 return page_;
353 }
354
355 } // namespace printing
356