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.
4
5 #include "chrome/browser/printing/print_job.h"
6
7 #include "base/bind.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/browser_thread.h"
16 #include "content/public/browser/notification_service.h"
17 #include "printing/printed_document.h"
18 #include "printing/printed_page.h"
19
20 using base::TimeDelta;
21
22 namespace {
23
24 // Helper function to ensure |owner| is valid until at least |callback| returns.
HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner> & owner,const base::Closure & callback)25 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
26 const base::Closure& callback) {
27 callback.Run();
28 }
29
30 } // namespace
31
32 namespace printing {
33
PrintJob()34 PrintJob::PrintJob()
35 : ui_message_loop_(base::MessageLoop::current()),
36 source_(NULL),
37 worker_(),
38 settings_(),
39 is_job_pending_(false),
40 is_canceling_(false),
41 quit_factory_(this) {
42 DCHECK(ui_message_loop_);
43 // This is normally a UI message loop, but in unit tests, the message loop is
44 // of the 'default' type.
45 DCHECK(base::MessageLoopForUI::IsCurrent() ||
46 ui_message_loop_->type() == base::MessageLoop::TYPE_DEFAULT);
47 ui_message_loop_->AddDestructionObserver(this);
48 }
49
~PrintJob()50 PrintJob::~PrintJob() {
51 ui_message_loop_->RemoveDestructionObserver(this);
52 // The job should be finished (or at least canceled) when it is destroyed.
53 DCHECK(!is_job_pending_);
54 DCHECK(!is_canceling_);
55 if (worker_.get())
56 DCHECK(worker_->message_loop() == NULL);
57 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
58 }
59
Initialize(PrintJobWorkerOwner * job,PrintedPagesSource * source,int page_count)60 void PrintJob::Initialize(PrintJobWorkerOwner* job,
61 PrintedPagesSource* source,
62 int page_count) {
63 DCHECK(!source_);
64 DCHECK(!worker_.get());
65 DCHECK(!is_job_pending_);
66 DCHECK(!is_canceling_);
67 DCHECK(!document_.get());
68 source_ = source;
69 worker_.reset(job->DetachWorker(this));
70 settings_ = job->settings();
71
72 PrintedDocument* new_doc =
73 new PrintedDocument(settings_,
74 source_,
75 job->cookie(),
76 content::BrowserThread::GetBlockingPool());
77 new_doc->set_page_count(page_count);
78 UpdatePrintedDocument(new_doc);
79
80 // Don't forget to register to our own messages.
81 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
82 content::Source<PrintJob>(this));
83 }
84
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)85 void PrintJob::Observe(int type,
86 const content::NotificationSource& source,
87 const content::NotificationDetails& details) {
88 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
89 switch (type) {
90 case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
91 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
92 break;
93 }
94 default: {
95 break;
96 }
97 }
98 }
99
GetSettingsDone(const PrintSettings & new_settings,PrintingContext::Result result)100 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
101 PrintingContext::Result result) {
102 NOTREACHED();
103 }
104
DetachWorker(PrintJobWorkerOwner * new_owner)105 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
106 NOTREACHED();
107 return NULL;
108 }
109
message_loop()110 base::MessageLoop* PrintJob::message_loop() {
111 return ui_message_loop_;
112 }
113
settings() const114 const PrintSettings& PrintJob::settings() const {
115 return settings_;
116 }
117
cookie() const118 int PrintJob::cookie() const {
119 if (!document_.get())
120 // Always use an invalid cookie in this case.
121 return 0;
122 return document_->cookie();
123 }
124
WillDestroyCurrentMessageLoop()125 void PrintJob::WillDestroyCurrentMessageLoop() {
126 NOTREACHED();
127 }
128
StartPrinting()129 void PrintJob::StartPrinting() {
130 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
131 DCHECK(worker_->message_loop());
132 DCHECK(!is_job_pending_);
133 if (!worker_->message_loop() || is_job_pending_)
134 return;
135
136 // Real work is done in PrintJobWorker::StartPrinting().
137 worker_->message_loop()->PostTask(
138 FROM_HERE,
139 base::Bind(&HoldRefCallback, make_scoped_refptr(this),
140 base::Bind(&PrintJobWorker::StartPrinting,
141 base::Unretained(worker_.get()), document_)));
142 // Set the flag right now.
143 is_job_pending_ = true;
144
145 // Tell everyone!
146 scoped_refptr<JobEventDetails> details(
147 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
148 content::NotificationService::current()->Notify(
149 chrome::NOTIFICATION_PRINT_JOB_EVENT,
150 content::Source<PrintJob>(this),
151 content::Details<JobEventDetails>(details.get()));
152 }
153
Stop()154 void PrintJob::Stop() {
155 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
156
157 if (quit_factory_.HasWeakPtrs()) {
158 // In case we're running a nested message loop to wait for a job to finish,
159 // and we finished before the timeout, quit the nested loop right away.
160 Quit();
161 quit_factory_.InvalidateWeakPtrs();
162 }
163
164 // Be sure to live long enough.
165 scoped_refptr<PrintJob> handle(this);
166
167 if (worker_->message_loop()) {
168 ControlledWorkerShutdown();
169 } else {
170 // Flush the cached document.
171 UpdatePrintedDocument(NULL);
172 }
173 }
174
Cancel()175 void PrintJob::Cancel() {
176 if (is_canceling_)
177 return;
178 is_canceling_ = true;
179
180 // Be sure to live long enough.
181 scoped_refptr<PrintJob> handle(this);
182
183 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
184 base::MessageLoop* worker_loop =
185 worker_.get() ? worker_->message_loop() : NULL;
186 if (worker_loop) {
187 // Call this right now so it renders the context invalid. Do not use
188 // InvokeLater since it would take too much time.
189 worker_->Cancel();
190 }
191 // Make sure a Cancel() is broadcast.
192 scoped_refptr<JobEventDetails> details(
193 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
194 content::NotificationService::current()->Notify(
195 chrome::NOTIFICATION_PRINT_JOB_EVENT,
196 content::Source<PrintJob>(this),
197 content::Details<JobEventDetails>(details.get()));
198 Stop();
199 is_canceling_ = false;
200 }
201
FlushJob(base::TimeDelta timeout)202 bool PrintJob::FlushJob(base::TimeDelta timeout) {
203 // Make sure the object outlive this message loop.
204 scoped_refptr<PrintJob> handle(this);
205
206 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
207 base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout);
208
209 base::MessageLoop::ScopedNestableTaskAllower allow(
210 base::MessageLoop::current());
211 base::MessageLoop::current()->Run();
212
213 return true;
214 }
215
DisconnectSource()216 void PrintJob::DisconnectSource() {
217 source_ = NULL;
218 if (document_.get())
219 document_->DisconnectSource();
220 }
221
is_job_pending() const222 bool PrintJob::is_job_pending() const {
223 return is_job_pending_;
224 }
225
document() const226 PrintedDocument* PrintJob::document() const {
227 return document_.get();
228 }
229
UpdatePrintedDocument(PrintedDocument * new_document)230 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
231 if (document_.get() == new_document)
232 return;
233
234 document_ = new_document;
235
236 if (document_.get()) {
237 settings_ = document_->settings();
238 }
239
240 if (worker_.get() && worker_->message_loop()) {
241 DCHECK(!is_job_pending_);
242 // Sync the document with the worker.
243 worker_->message_loop()->PostTask(
244 FROM_HERE,
245 base::Bind(&HoldRefCallback, make_scoped_refptr(this),
246 base::Bind(&PrintJobWorker::OnDocumentChanged,
247 base::Unretained(worker_.get()), document_)));
248 }
249 }
250
OnNotifyPrintJobEvent(const JobEventDetails & event_details)251 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
252 switch (event_details.type()) {
253 case JobEventDetails::FAILED: {
254 settings_.Clear();
255 // No need to cancel since the worker already canceled itself.
256 Stop();
257 break;
258 }
259 case JobEventDetails::USER_INIT_DONE:
260 case JobEventDetails::DEFAULT_INIT_DONE:
261 case JobEventDetails::USER_INIT_CANCELED: {
262 DCHECK_EQ(event_details.document(), document_.get());
263 break;
264 }
265 case JobEventDetails::NEW_DOC:
266 case JobEventDetails::NEW_PAGE:
267 case JobEventDetails::PAGE_DONE:
268 case JobEventDetails::JOB_DONE:
269 case JobEventDetails::ALL_PAGES_REQUESTED: {
270 // Don't care.
271 break;
272 }
273 case JobEventDetails::DOC_DONE: {
274 // This will call Stop() and broadcast a JOB_DONE message.
275 base::MessageLoop::current()->PostTask(
276 FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
277 break;
278 }
279 default: {
280 NOTREACHED();
281 break;
282 }
283 }
284 }
285
OnDocumentDone()286 void PrintJob::OnDocumentDone() {
287 // Be sure to live long enough. The instance could be destroyed by the
288 // JOB_DONE broadcast.
289 scoped_refptr<PrintJob> handle(this);
290
291 // Stop the worker thread.
292 Stop();
293
294 scoped_refptr<JobEventDetails> details(
295 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
296 content::NotificationService::current()->Notify(
297 chrome::NOTIFICATION_PRINT_JOB_EVENT,
298 content::Source<PrintJob>(this),
299 content::Details<JobEventDetails>(details.get()));
300 }
301
ControlledWorkerShutdown()302 void PrintJob::ControlledWorkerShutdown() {
303 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
304
305 // The deadlock this code works around is specific to window messaging on
306 // Windows, so we aren't likely to need it on any other platforms.
307 #if defined(OS_WIN)
308 // We could easily get into a deadlock case if worker_->Stop() is used; the
309 // printer driver created a window as a child of the browser window. By
310 // canceling the job, the printer driver initiated dialog box is destroyed,
311 // which sends a blocking message to its parent window. If the browser window
312 // thread is not processing messages, a deadlock occurs.
313 //
314 // This function ensures that the dialog box will be destroyed in a timely
315 // manner by the mere fact that the thread will terminate. So the potential
316 // deadlock is eliminated.
317 worker_->StopSoon();
318
319 // Delay shutdown until the worker terminates. We want this code path
320 // to wait on the thread to quit before continuing.
321 if (worker_->IsRunning()) {
322 base::MessageLoop::current()->PostDelayedTask(
323 FROM_HERE,
324 base::Bind(&PrintJob::ControlledWorkerShutdown, this),
325 base::TimeDelta::FromMilliseconds(100));
326 return;
327 }
328 #endif
329
330
331 // Now make sure the thread object is cleaned up. Do this on a worker
332 // thread because it may block.
333 base::WorkerPool::PostTaskAndReply(
334 FROM_HERE,
335 base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())),
336 base::Bind(&PrintJob::HoldUntilStopIsCalled, this),
337 false);
338
339 is_job_pending_ = false;
340 registrar_.RemoveAll();
341 UpdatePrintedDocument(NULL);
342 }
343
HoldUntilStopIsCalled()344 void PrintJob::HoldUntilStopIsCalled() {
345 }
346
Quit()347 void PrintJob::Quit() {
348 base::MessageLoop::current()->Quit();
349 }
350
351 // Takes settings_ ownership and will be deleted in the receiving thread.
JobEventDetails(Type type,PrintedDocument * document,PrintedPage * page)352 JobEventDetails::JobEventDetails(Type type,
353 PrintedDocument* document,
354 PrintedPage* page)
355 : document_(document),
356 page_(page),
357 type_(type) {
358 }
359
~JobEventDetails()360 JobEventDetails::~JobEventDetails() {
361 }
362
document() const363 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
364
page() const365 PrintedPage* JobEventDetails::page() const { return page_.get(); }
366
367 } // namespace printing
368