Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / printing / print_job.cc
bloba899b1728c3529484c98d1efcf3ce946d434c590
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/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;
21 namespace {
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) {
26 callback.Run();
29 } // namespace
31 namespace printing {
33 PrintJob::PrintJob()
34 : ui_message_loop_(base::MessageLoop::current()),
35 source_(NULL),
36 worker_(),
37 settings_(),
38 is_job_pending_(false),
39 is_canceling_(false),
40 is_stopping_(false),
41 is_stopped_(false),
42 quit_factory_(this),
43 weak_ptr_factory_(this) {
44 DCHECK(ui_message_loop_);
45 // This is normally a UI message loop, but in unit tests, the message loop is
46 // of the 'default' type.
47 DCHECK(base::MessageLoopForUI::IsCurrent() ||
48 ui_message_loop_->type() == base::MessageLoop::TYPE_DEFAULT);
49 ui_message_loop_->AddDestructionObserver(this);
52 PrintJob::~PrintJob() {
53 ui_message_loop_->RemoveDestructionObserver(this);
54 // The job should be finished (or at least canceled) when it is destroyed.
55 DCHECK(!is_job_pending_);
56 DCHECK(!is_canceling_);
57 if (worker_.get())
58 DCHECK(worker_->message_loop() == NULL);
59 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
62 void PrintJob::Initialize(PrintJobWorkerOwner* job,
63 PrintedPagesSource* source,
64 int page_count) {
65 DCHECK(!source_);
66 DCHECK(!worker_.get());
67 DCHECK(!is_job_pending_);
68 DCHECK(!is_canceling_);
69 DCHECK(!document_.get());
70 source_ = source;
71 worker_.reset(job->DetachWorker(this));
72 settings_ = job->settings();
74 PrintedDocument* new_doc =
75 new PrintedDocument(settings_, source_, job->cookie());
76 new_doc->set_page_count(page_count);
77 UpdatePrintedDocument(new_doc);
79 // Don't forget to register to our own messages.
80 registrar_.Add(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
81 content::Source<PrintJob>(this));
84 void PrintJob::Observe(int type,
85 const content::NotificationSource& source,
86 const content::NotificationDetails& details) {
87 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
88 switch (type) {
89 case chrome::NOTIFICATION_PRINT_JOB_EVENT: {
90 OnNotifyPrintJobEvent(*content::Details<JobEventDetails>(details).ptr());
91 break;
93 default: {
94 break;
99 void PrintJob::GetSettingsDone(const PrintSettings& new_settings,
100 PrintingContext::Result result) {
101 NOTREACHED();
104 PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) {
105 NOTREACHED();
106 return NULL;
109 base::MessageLoop* PrintJob::message_loop() {
110 return ui_message_loop_;
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::WillDestroyCurrentMessageLoop() {
125 NOTREACHED();
128 void PrintJob::StartPrinting() {
129 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
130 DCHECK(worker_->message_loop());
131 DCHECK(!is_job_pending_);
132 if (!worker_->message_loop() || is_job_pending_)
133 return;
135 // Real work is done in PrintJobWorker::StartPrinting().
136 worker_->message_loop()->PostTask(
137 FROM_HERE,
138 base::Bind(&HoldRefCallback, make_scoped_refptr(this),
139 base::Bind(&PrintJobWorker::StartPrinting,
140 base::Unretained(worker_.get()), document_)));
141 // Set the flag right now.
142 is_job_pending_ = true;
144 // Tell everyone!
145 scoped_refptr<JobEventDetails> details(
146 new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL));
147 content::NotificationService::current()->Notify(
148 chrome::NOTIFICATION_PRINT_JOB_EVENT,
149 content::Source<PrintJob>(this),
150 content::Details<JobEventDetails>(details.get()));
153 void PrintJob::Stop() {
154 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
156 if (quit_factory_.HasWeakPtrs()) {
157 // In case we're running a nested message loop to wait for a job to finish,
158 // and we finished before the timeout, quit the nested loop right away.
159 Quit();
160 quit_factory_.InvalidateWeakPtrs();
163 // Be sure to live long enough.
164 scoped_refptr<PrintJob> handle(this);
166 base::MessageLoop* worker_loop = worker_->message_loop();
167 if (worker_loop) {
168 ControlledWorkerShutdown();
170 is_job_pending_ = false;
171 registrar_.Remove(this, chrome::NOTIFICATION_PRINT_JOB_EVENT,
172 content::Source<PrintJob>(this));
174 // Flush the cached document.
175 UpdatePrintedDocument(NULL);
178 void PrintJob::Cancel() {
179 if (is_canceling_)
180 return;
181 is_canceling_ = true;
183 // Be sure to live long enough.
184 scoped_refptr<PrintJob> handle(this);
186 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
187 base::MessageLoop* worker_loop =
188 worker_.get() ? worker_->message_loop() : NULL;
189 if (worker_loop) {
190 // Call this right now so it renders the context invalid. Do not use
191 // InvokeLater since it would take too much time.
192 worker_->Cancel();
194 // Make sure a Cancel() is broadcast.
195 scoped_refptr<JobEventDetails> details(
196 new JobEventDetails(JobEventDetails::FAILED, NULL, NULL));
197 content::NotificationService::current()->Notify(
198 chrome::NOTIFICATION_PRINT_JOB_EVENT,
199 content::Source<PrintJob>(this),
200 content::Details<JobEventDetails>(details.get()));
201 Stop();
202 is_canceling_ = false;
205 bool PrintJob::FlushJob(base::TimeDelta timeout) {
206 // Make sure the object outlive this message loop.
207 scoped_refptr<PrintJob> handle(this);
209 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
210 base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), timeout);
212 base::MessageLoop::ScopedNestableTaskAllower allow(
213 base::MessageLoop::current());
214 base::MessageLoop::current()->Run();
216 return true;
219 void PrintJob::DisconnectSource() {
220 source_ = NULL;
221 if (document_.get())
222 document_->DisconnectSource();
225 bool PrintJob::is_job_pending() const {
226 return is_job_pending_;
229 bool PrintJob::is_stopping() const {
230 return is_stopping_;
233 bool PrintJob::is_stopped() const {
234 return is_stopped_;
237 PrintedDocument* PrintJob::document() const {
238 return document_.get();
241 void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) {
242 if (document_.get() == new_document)
243 return;
245 document_ = new_document;
247 if (document_.get()) {
248 settings_ = document_->settings();
251 if (worker_.get() && worker_->message_loop()) {
252 DCHECK(!is_job_pending_);
253 // Sync the document with the worker.
254 worker_->message_loop()->PostTask(
255 FROM_HERE,
256 base::Bind(&HoldRefCallback, make_scoped_refptr(this),
257 base::Bind(&PrintJobWorker::OnDocumentChanged,
258 base::Unretained(worker_.get()), document_)));
262 void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
263 switch (event_details.type()) {
264 case JobEventDetails::FAILED: {
265 settings_.Clear();
266 // No need to cancel since the worker already canceled itself.
267 Stop();
268 break;
270 case JobEventDetails::USER_INIT_DONE:
271 case JobEventDetails::DEFAULT_INIT_DONE:
272 case JobEventDetails::USER_INIT_CANCELED: {
273 DCHECK_EQ(event_details.document(), document_.get());
274 break;
276 case JobEventDetails::NEW_DOC:
277 case JobEventDetails::NEW_PAGE:
278 case JobEventDetails::PAGE_DONE:
279 case JobEventDetails::JOB_DONE:
280 case JobEventDetails::ALL_PAGES_REQUESTED: {
281 // Don't care.
282 break;
284 case JobEventDetails::DOC_DONE: {
285 // This will call Stop() and broadcast a JOB_DONE message.
286 base::MessageLoop::current()->PostTask(
287 FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this));
288 break;
290 default: {
291 NOTREACHED();
292 break;
297 void PrintJob::OnDocumentDone() {
298 // Be sure to live long enough. The instance could be destroyed by the
299 // JOB_DONE broadcast.
300 scoped_refptr<PrintJob> handle(this);
302 // Stop the worker thread.
303 Stop();
305 scoped_refptr<JobEventDetails> details(
306 new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL));
307 content::NotificationService::current()->Notify(
308 chrome::NOTIFICATION_PRINT_JOB_EVENT,
309 content::Source<PrintJob>(this),
310 content::Details<JobEventDetails>(details.get()));
313 void PrintJob::ControlledWorkerShutdown() {
314 DCHECK_EQ(ui_message_loop_, base::MessageLoop::current());
316 // The deadlock this code works around is specific to window messaging on
317 // Windows, so we aren't likely to need it on any other platforms.
318 #if defined(OS_WIN)
319 // We could easily get into a deadlock case if worker_->Stop() is used; the
320 // printer driver created a window as a child of the browser window. By
321 // canceling the job, the printer driver initiated dialog box is destroyed,
322 // which sends a blocking message to its parent window. If the browser window
323 // thread is not processing messages, a deadlock occurs.
325 // This function ensures that the dialog box will be destroyed in a timely
326 // manner by the mere fact that the thread will terminate. So the potential
327 // deadlock is eliminated.
328 worker_->StopSoon();
330 // Run a tight message loop until the worker terminates. It may seems like a
331 // hack but I see no other way to get it to work flawlessly. The issues here
332 // are:
333 // - We don't want to run tasks while the thread is quitting.
334 // - We want this code path to wait on the thread to quit before continuing.
335 MSG msg;
336 HANDLE thread_handle = worker_->thread_handle().platform_handle();
337 for (; thread_handle;) {
338 // Note that we don't do any kind of message prioritization since we don't
339 // execute any pending task or timer.
340 DWORD result = MsgWaitForMultipleObjects(1, &thread_handle,
341 FALSE, INFINITE, QS_ALLINPUT);
342 if (result == WAIT_OBJECT_0 + 1) {
343 while (PeekMessage(&msg, NULL, 0, 0, TRUE) > 0) {
344 TranslateMessage(&msg);
345 DispatchMessage(&msg);
347 // Continue looping.
348 } else if (result == WAIT_OBJECT_0) {
349 // The thread quit.
350 break;
351 } else {
352 // An error occurred. Assume the thread quit.
353 NOTREACHED();
354 break;
357 #endif
360 // Now make sure the thread object is cleaned up. Do this on a worker
361 // thread because it may block.
362 is_stopping_ = true;
364 base::WorkerPool::PostTaskAndReply(
365 FROM_HERE,
366 base::Bind(&PrintJobWorker::Stop,
367 base::Unretained(worker_.get())),
368 base::Bind(&PrintJob::HoldUntilStopIsCalled,
369 weak_ptr_factory_.GetWeakPtr(),
370 scoped_refptr<PrintJob>(this)),
371 false);
374 void PrintJob::HoldUntilStopIsCalled(const scoped_refptr<PrintJob>&) {
375 is_stopped_ = true;
376 is_stopping_ = false;
379 void PrintJob::Quit() {
380 base::MessageLoop::current()->Quit();
383 // Takes settings_ ownership and will be deleted in the receiving thread.
384 JobEventDetails::JobEventDetails(Type type,
385 PrintedDocument* document,
386 PrintedPage* page)
387 : document_(document),
388 page_(page),
389 type_(type) {
392 JobEventDetails::~JobEventDetails() {
395 PrintedDocument* JobEventDetails::document() const { return document_.get(); }
397 PrintedPage* JobEventDetails::page() const { return page_.get(); }
399 } // namespace printing