• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_worker.h"
6 
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/callback.h"
10 #include "base/compiler_specific.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/values.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/printing/print_job.h"
16 #include "chrome/browser/printing/printing_ui_web_contents_observer.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_service.h"
19 #include "grit/generated_resources.h"
20 #include "printing/print_job_constants.h"
21 #include "printing/printed_document.h"
22 #include "printing/printed_page.h"
23 #include "printing/printing_utils.h"
24 #include "ui/base/l10n/l10n_util.h"
25 
26 using content::BrowserThread;
27 
28 namespace {
29 
30 // Helper function to ensure |owner| is valid until at least |callback| returns.
HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner> & owner,const base::Closure & callback)31 void HoldRefCallback(const scoped_refptr<printing::PrintJobWorkerOwner>& owner,
32                      const base::Closure& callback) {
33   callback.Run();
34 }
35 
36 }  // namespace
37 
38 namespace printing {
39 
NotificationCallback(PrintJobWorkerOwner * print_job,JobEventDetails::Type detail_type,PrintedDocument * document,PrintedPage * page)40 void NotificationCallback(PrintJobWorkerOwner* print_job,
41                           JobEventDetails::Type detail_type,
42                           PrintedDocument* document,
43                           PrintedPage* page) {
44   JobEventDetails* details = new JobEventDetails(detail_type, document, page);
45   content::NotificationService::current()->Notify(
46       chrome::NOTIFICATION_PRINT_JOB_EVENT,
47       // We know that is is a PrintJob object in this circumstance.
48       content::Source<PrintJob>(static_cast<PrintJob*>(print_job)),
49       content::Details<JobEventDetails>(details));
50 }
51 
PrintJobWorker(PrintJobWorkerOwner * owner)52 PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner)
53     : Thread("Printing_Worker"),
54       owner_(owner),
55       weak_factory_(this) {
56   // The object is created in the IO thread.
57   DCHECK_EQ(owner_->message_loop(), base::MessageLoop::current());
58 
59   printing_context_.reset(PrintingContext::Create(
60       g_browser_process->GetApplicationLocale()));
61 }
62 
~PrintJobWorker()63 PrintJobWorker::~PrintJobWorker() {
64   // The object is normally deleted in the UI thread, but when the user
65   // cancels printing or in the case of print preview, the worker is destroyed
66   // on the I/O thread.
67   DCHECK_EQ(owner_->message_loop(), base::MessageLoop::current());
68   Stop();
69 }
70 
SetNewOwner(PrintJobWorkerOwner * new_owner)71 void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) {
72   DCHECK(page_number_ == PageNumber::npos());
73   owner_ = new_owner;
74 }
75 
SetPrintDestination(PrintDestinationInterface * destination)76 void PrintJobWorker::SetPrintDestination(
77     PrintDestinationInterface* destination) {
78   destination_ = destination;
79 }
80 
GetSettings(bool ask_user_for_settings,scoped_ptr<PrintingUIWebContentsObserver> web_contents_observer,int document_page_count,bool has_selection,MarginType margin_type)81 void PrintJobWorker::GetSettings(
82     bool ask_user_for_settings,
83     scoped_ptr<PrintingUIWebContentsObserver> web_contents_observer,
84     int document_page_count,
85     bool has_selection,
86     MarginType margin_type) {
87   DCHECK_EQ(message_loop(), base::MessageLoop::current());
88   DCHECK_EQ(page_number_, PageNumber::npos());
89 
90   // Recursive task processing is needed for the dialog in case it needs to be
91   // destroyed by a task.
92   // TODO(thestig): This code is wrong. SetNestableTasksAllowed(true) is needed
93   // on the thread where the PrintDlgEx is called, and definitely both calls
94   // should happen on the same thread. See http://crbug.com/73466
95   // MessageLoop::current()->SetNestableTasksAllowed(true);
96   printing_context_->set_margin_type(margin_type);
97 
98   // When we delegate to a destination, we don't ask the user for settings.
99   // TODO(mad): Ask the destination for settings.
100   if (ask_user_for_settings && destination_.get() == NULL) {
101     BrowserThread::PostTask(
102         BrowserThread::UI, FROM_HERE,
103         base::Bind(&HoldRefCallback, make_scoped_refptr(owner_),
104                    base::Bind(&PrintJobWorker::GetSettingsWithUI,
105                               base::Unretained(this),
106                               base::Passed(&web_contents_observer),
107                               document_page_count,
108                               has_selection)));
109   } else {
110     BrowserThread::PostTask(
111         BrowserThread::UI, FROM_HERE,
112         base::Bind(&HoldRefCallback, make_scoped_refptr(owner_),
113                    base::Bind(&PrintJobWorker::UseDefaultSettings,
114                               base::Unretained(this))));
115   }
116 }
117 
SetSettings(const base::DictionaryValue * const new_settings)118 void PrintJobWorker::SetSettings(
119     const base::DictionaryValue* const new_settings) {
120   DCHECK_EQ(message_loop(), base::MessageLoop::current());
121 
122   BrowserThread::PostTask(
123       BrowserThread::UI,
124       FROM_HERE,
125       base::Bind(&HoldRefCallback,
126                  make_scoped_refptr(owner_),
127                  base::Bind(&PrintJobWorker::UpdatePrintSettings,
128                             base::Unretained(this),
129                             base::Owned(new_settings))));
130 }
131 
UpdatePrintSettings(const base::DictionaryValue * const new_settings)132 void PrintJobWorker::UpdatePrintSettings(
133     const base::DictionaryValue* const new_settings) {
134   PrintingContext::Result result =
135       printing_context_->UpdatePrintSettings(*new_settings);
136   GetSettingsDone(result);
137 }
138 
GetSettingsDone(PrintingContext::Result result)139 void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) {
140   // Most PrintingContext functions may start a message loop and process
141   // message recursively, so disable recursive task processing.
142   // TODO(thestig): See above comment. SetNestableTasksAllowed(false) needs to
143   // be called on the same thread as the previous call.  See
144   // http://crbug.com/73466
145   // MessageLoop::current()->SetNestableTasksAllowed(false);
146 
147   // We can't use OnFailure() here since owner_ may not support notifications.
148 
149   // PrintJob will create the new PrintedDocument.
150   owner_->message_loop()->PostTask(
151       FROM_HERE,
152       base::Bind(&PrintJobWorkerOwner::GetSettingsDone,
153                  make_scoped_refptr(owner_), printing_context_->settings(),
154                  result));
155 }
156 
GetSettingsWithUI(scoped_ptr<PrintingUIWebContentsObserver> web_contents_observer,int document_page_count,bool has_selection)157 void PrintJobWorker::GetSettingsWithUI(
158     scoped_ptr<PrintingUIWebContentsObserver> web_contents_observer,
159     int document_page_count,
160     bool has_selection) {
161   DCHECK_CURRENTLY_ON(BrowserThread::UI);
162 
163   gfx::NativeView parent_view = web_contents_observer->GetParentView();
164   if (!parent_view) {
165     GetSettingsWithUIDone(printing::PrintingContext::FAILED);
166     return;
167   }
168   printing_context_->AskUserForSettings(
169       parent_view, document_page_count, has_selection,
170       base::Bind(&PrintJobWorker::GetSettingsWithUIDone,
171                  base::Unretained(this)));
172 }
173 
GetSettingsWithUIDone(PrintingContext::Result result)174 void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) {
175   message_loop()->PostTask(
176       FROM_HERE,
177       base::Bind(&HoldRefCallback, make_scoped_refptr(owner_),
178                  base::Bind(&PrintJobWorker::GetSettingsDone,
179                             base::Unretained(this), result)));
180 }
181 
UseDefaultSettings()182 void PrintJobWorker::UseDefaultSettings() {
183   PrintingContext::Result result = printing_context_->UseDefaultSettings();
184   GetSettingsDone(result);
185 }
186 
StartPrinting(PrintedDocument * new_document)187 void PrintJobWorker::StartPrinting(PrintedDocument* new_document) {
188   DCHECK_EQ(message_loop(), base::MessageLoop::current());
189   DCHECK_EQ(page_number_, PageNumber::npos());
190   DCHECK_EQ(document_, new_document);
191   DCHECK(document_.get());
192 
193   if (!document_.get() || page_number_ != PageNumber::npos() ||
194       document_.get() != new_document) {
195     return;
196   }
197 
198   base::string16 document_name =
199       printing::SimplifyDocumentTitle(document_->name());
200   if (document_name.empty()) {
201     document_name = printing::SimplifyDocumentTitle(
202         l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE));
203   }
204   PrintingContext::Result result =
205       printing_context_->NewDocument(document_name);
206   if (result != PrintingContext::OK) {
207     OnFailure();
208     return;
209   }
210 
211   // Try to print already cached data. It may already have been generated for
212   // the print preview.
213   OnNewPage();
214   // Don't touch this anymore since the instance could be destroyed. It happens
215   // if all the pages are printed a one sweep and the client doesn't have a
216   // handle to us anymore. There's a timing issue involved between the worker
217   // thread and the UI thread. Take no chance.
218 }
219 
OnDocumentChanged(PrintedDocument * new_document)220 void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) {
221   DCHECK_EQ(message_loop(), base::MessageLoop::current());
222   DCHECK_EQ(page_number_, PageNumber::npos());
223 
224   if (page_number_ != PageNumber::npos())
225     return;
226 
227   document_ = new_document;
228 }
229 
OnNewPage()230 void PrintJobWorker::OnNewPage() {
231   if (!document_.get())  // Spurious message.
232     return;
233 
234   // message_loop() could return NULL when the print job is cancelled.
235   DCHECK_EQ(message_loop(), base::MessageLoop::current());
236 
237   if (page_number_ == PageNumber::npos()) {
238     // Find first page to print.
239     int page_count = document_->page_count();
240     if (!page_count) {
241       // We still don't know how many pages the document contains. We can't
242       // start to print the document yet since the header/footer may refer to
243       // the document's page count.
244       return;
245     }
246     // We have enough information to initialize page_number_.
247     page_number_.Init(document_->settings(), page_count);
248     if (destination_.get() != NULL)
249       destination_->SetPageCount(page_count);
250   }
251   DCHECK_NE(page_number_, PageNumber::npos());
252 
253   while (true) {
254     // Is the page available?
255     scoped_refptr<PrintedPage> page = document_->GetPage(page_number_.ToInt());
256     if (!page) {
257       // We need to wait for the page to be available.
258       base::MessageLoop::current()->PostDelayedTask(
259           FROM_HERE,
260           base::Bind(&PrintJobWorker::OnNewPage, weak_factory_.GetWeakPtr()),
261           base::TimeDelta::FromMilliseconds(500));
262       break;
263     }
264     // The page is there, print it.
265     SpoolPage(page.get());
266     ++page_number_;
267     if (page_number_ == PageNumber::npos()) {
268       OnDocumentDone();
269       // Don't touch this anymore since the instance could be destroyed.
270       break;
271     }
272   }
273 }
274 
Cancel()275 void PrintJobWorker::Cancel() {
276   // This is the only function that can be called from any thread.
277   printing_context_->Cancel();
278   // Cannot touch any member variable since we don't know in which thread
279   // context we run.
280 }
281 
OnDocumentDone()282 void PrintJobWorker::OnDocumentDone() {
283   DCHECK_EQ(message_loop(), base::MessageLoop::current());
284   DCHECK_EQ(page_number_, PageNumber::npos());
285   DCHECK(document_.get());
286 
287   if (printing_context_->DocumentDone() != PrintingContext::OK) {
288     OnFailure();
289     return;
290   }
291 
292   owner_->message_loop()->PostTask(
293       FROM_HERE, base::Bind(NotificationCallback, make_scoped_refptr(owner_),
294                             JobEventDetails::DOC_DONE, document_,
295                             scoped_refptr<PrintedPage>()));
296 
297   // Makes sure the variables are reinitialized.
298   document_ = NULL;
299 }
300 
SpoolPage(PrintedPage * page)301 void PrintJobWorker::SpoolPage(PrintedPage* page) {
302   DCHECK_EQ(message_loop(), base::MessageLoop::current());
303   DCHECK_NE(page_number_, PageNumber::npos());
304 
305   // Signal everyone that the page is about to be printed.
306   owner_->message_loop()->PostTask(
307       FROM_HERE, base::Bind(NotificationCallback, make_scoped_refptr(owner_),
308                             JobEventDetails::NEW_PAGE, document_,
309                             make_scoped_refptr(page)));
310 
311   // Preprocess.
312   if (printing_context_->NewPage() != PrintingContext::OK) {
313     OnFailure();
314     return;
315   }
316 
317   if (destination_.get() != NULL) {
318     std::vector<uint8> metabytes(page->metafile()->GetDataSize());
319     bool success = page->metafile()->GetData(
320         reinterpret_cast<void*>(&metabytes[0]), metabytes.size());
321     DCHECK(success) << "Failed to get metafile data.";
322     destination_->SetPageContent(
323         page->page_number(),
324         reinterpret_cast<void*>(&metabytes[0]),
325         metabytes.size());
326     return;
327   }
328 
329   // Actual printing.
330 #if defined(OS_WIN) || defined(OS_MACOSX)
331   document_->RenderPrintedPage(*page, printing_context_->context());
332 #elif defined(OS_POSIX)
333   document_->RenderPrintedPage(*page, printing_context_.get());
334 #endif
335 
336   // Postprocess.
337   if (printing_context_->PageDone() != PrintingContext::OK) {
338     OnFailure();
339     return;
340   }
341 
342   // Signal everyone that the page is printed.
343   owner_->message_loop()->PostTask(
344       FROM_HERE,
345       base::Bind(NotificationCallback, make_scoped_refptr(owner_),
346                  JobEventDetails::PAGE_DONE, document_,
347                  make_scoped_refptr(page)));
348 }
349 
OnFailure()350 void PrintJobWorker::OnFailure() {
351   DCHECK_EQ(message_loop(), base::MessageLoop::current());
352 
353   // We may loose our last reference by broadcasting the FAILED event.
354   scoped_refptr<PrintJobWorkerOwner> handle(owner_);
355 
356   owner_->message_loop()->PostTask(
357       FROM_HERE, base::Bind(NotificationCallback, make_scoped_refptr(owner_),
358                             JobEventDetails::FAILED, document_,
359                             scoped_refptr<PrintedPage>()));
360   Cancel();
361 
362   // Makes sure the variables are reinitialized.
363   document_ = NULL;
364   page_number_ = PageNumber::npos();
365 }
366 
367 }  // namespace printing
368