// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/printing/print_job_worker.h" #include "base/message_loop.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/printing/print_job.h" #include "content/browser/browser_thread.h" #include "content/common/notification_service.h" #include "printing/printed_document.h" #include "printing/printed_page.h" namespace printing { class PrintJobWorker::NotificationTask : public Task { public: NotificationTask() : print_job_(NULL), details_(NULL) { } ~NotificationTask() { } // Initializes the object. This object can't be initialized in the constructor // since it is not created directly. void Init(PrintJobWorkerOwner* print_job, JobEventDetails::Type detail_type, PrintedDocument* document, PrintedPage* page) { DCHECK(!print_job_); DCHECK(!details_); print_job_ = print_job; details_ = new JobEventDetails(detail_type, document, page); } virtual void Run() { // Send the notification in the right thread. NotificationService::current()->Notify( NotificationType::PRINT_JOB_EVENT, // We know that is is a PrintJob object in this circumstance. Source<PrintJob>(static_cast<PrintJob*>(print_job_.get())), Details<JobEventDetails>(details_)); } // The job which originates this notification. scoped_refptr<PrintJobWorkerOwner> print_job_; scoped_refptr<JobEventDetails> details_; }; PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner) : Thread("Printing_Worker"), owner_(owner) { // The object is created in the IO thread. DCHECK_EQ(owner_->message_loop(), MessageLoop::current()); printing_context_.reset(PrintingContext::Create( g_browser_process->GetApplicationLocale())); } PrintJobWorker::~PrintJobWorker() { // The object is normally deleted in the UI thread, but when the user // cancels printing or in the case of print preview, the worker is destroyed // on the I/O thread. DCHECK_EQ(owner_->message_loop(), MessageLoop::current()); } void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) { DCHECK(page_number_ == PageNumber::npos()); owner_ = new_owner; } void PrintJobWorker::GetSettings(bool ask_user_for_settings, gfx::NativeView parent_view, int document_page_count, bool has_selection, bool use_overlays) { DCHECK_EQ(message_loop(), MessageLoop::current()); DCHECK_EQ(page_number_, PageNumber::npos()); // Recursive task processing is needed for the dialog in case it needs to be // destroyed by a task. // TODO(thestig): this code is wrong, SetNestableTasksAllowed(true) is needed // on the thread where the PrintDlgEx is called, and definitely both calls // should happen on the same thread. See http://crbug.com/73466 // MessageLoop::current()->SetNestableTasksAllowed(true); printing_context_->set_use_overlays(use_overlays); if (ask_user_for_settings) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &PrintJobWorker::GetSettingsWithUI, parent_view, document_page_count, has_selection)); } else { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &PrintJobWorker::UseDefaultSettings)); } } void PrintJobWorker::SetSettings(const DictionaryValue* const new_settings) { DCHECK_EQ(message_loop(), MessageLoop::current()); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &PrintJobWorker::UpdatePrintSettings, new_settings)); } void PrintJobWorker::UpdatePrintSettings( const DictionaryValue* const new_settings) { // Create new PageRanges based on |new_settings|. PageRanges new_ranges; ListValue* page_range_array; if (new_settings->GetList("pageRange", &page_range_array)) { for (size_t index = 0; index < page_range_array->GetSize(); ++index) { DictionaryValue* dict; if (page_range_array->GetDictionary(index, &dict)) { PageRange range; if (dict->GetInteger("from", &range.from) && dict->GetInteger("to", &range.to)) { // Page numbers are 0-based. range.from--; range.to--; new_ranges.push_back(range); } } } } PrintingContext::Result result = printing_context_->UpdatePrintSettings(*new_settings, new_ranges); delete new_settings; GetSettingsDone(result); } void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) { // Most PrintingContext functions may start a message loop and process // message recursively, so disable recursive task processing. // TODO(thestig): see above comment. SetNestableTasksAllowed(false) needs to // be called on the same thread as the previous call. See // http://crbug.com/73466 // MessageLoop::current()->SetNestableTasksAllowed(false); // We can't use OnFailure() here since owner_ may not support notifications. // PrintJob will create the new PrintedDocument. owner_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( owner_, &PrintJobWorkerOwner::GetSettingsDone, printing_context_->settings(), result)); } void PrintJobWorker::GetSettingsWithUI(gfx::NativeView parent_view, int document_page_count, bool has_selection) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); printing_context_->AskUserForSettings( parent_view, document_page_count, has_selection, NewCallback(this, &PrintJobWorker::GetSettingsWithUIDone)); } void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) { message_loop()->PostTask(FROM_HERE, NewRunnableMethod( this, &PrintJobWorker::GetSettingsDone, result)); } void PrintJobWorker::UseDefaultSettings() { PrintingContext::Result result = printing_context_->UseDefaultSettings(); GetSettingsDone(result); } void PrintJobWorker::StartPrinting(PrintedDocument* new_document) { DCHECK_EQ(message_loop(), MessageLoop::current()); DCHECK_EQ(page_number_, PageNumber::npos()); DCHECK_EQ(document_, new_document); DCHECK(document_.get()); DCHECK(new_document->settings().Equals(printing_context_->settings())); if (!document_.get() || page_number_ != PageNumber::npos() || document_ != new_document) { return; } PrintingContext::Result result = printing_context_->NewDocument(document_->name()); if (result != PrintingContext::OK) { OnFailure(); return; } // Try to print already cached data. It may already have been generated for // the print preview. OnNewPage(); // Don't touch this anymore since the instance could be destroyed. It happens // if all the pages are printed a one sweep and the client doesn't have a // handle to us anymore. There's a timing issue involved between the worker // thread and the UI thread. Take no chance. } void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) { DCHECK_EQ(message_loop(), MessageLoop::current()); DCHECK_EQ(page_number_, PageNumber::npos()); DCHECK(!new_document || new_document->settings().Equals(printing_context_->settings())); if (page_number_ != PageNumber::npos()) return; document_ = new_document; } void PrintJobWorker::OnNewPage() { if (!document_.get()) { // Spurious message. return; } // message_loop() could return NULL when the print job is cancelled. DCHECK_EQ(message_loop(), MessageLoop::current()); if (page_number_ == PageNumber::npos()) { // Find first page to print. int page_count = document_->page_count(); if (!page_count) { // We still don't know how many pages the document contains. We can't // start to print the document yet since the header/footer may refer to // the document's page count. return; } // We have enough information to initialize page_number_. page_number_.Init(document_->settings(), page_count); } DCHECK_NE(page_number_, PageNumber::npos()); for (;;) { // Is the page available? scoped_refptr<PrintedPage> page; if (!document_->GetPage(page_number_.ToInt(), &page)) { // We need to wait for the page to be available. MessageLoop::current()->PostDelayedTask( FROM_HERE, NewRunnableMethod(this, &PrintJobWorker::OnNewPage), 500); break; } // The page is there, print it. SpoolPage(*page); ++page_number_; if (page_number_ == PageNumber::npos()) { OnDocumentDone(); // Don't touch this anymore since the instance could be destroyed. break; } } } void PrintJobWorker::Cancel() { // This is the only function that can be called from any thread. printing_context_->Cancel(); // Cannot touch any member variable since we don't know in which thread // context we run. } void PrintJobWorker::OnDocumentDone() { DCHECK_EQ(message_loop(), MessageLoop::current()); DCHECK_EQ(page_number_, PageNumber::npos()); DCHECK(document_.get()); if (printing_context_->DocumentDone() != PrintingContext::OK) { OnFailure(); return; } // Tell everyone! NotificationTask* task = new NotificationTask(); task->Init(owner_, JobEventDetails::DOC_DONE, document_.get(), NULL); owner_->message_loop()->PostTask(FROM_HERE, task); // Makes sure the variables are reinitialized. document_ = NULL; } void PrintJobWorker::SpoolPage(PrintedPage& page) { DCHECK_EQ(message_loop(), MessageLoop::current()); DCHECK_NE(page_number_, PageNumber::npos()); // Signal everyone that the page is about to be printed. NotificationTask* task = new NotificationTask(); task->Init(owner_, JobEventDetails::NEW_PAGE, document_.get(), &page); owner_->message_loop()->PostTask(FROM_HERE, task); // Preprocess. if (printing_context_->NewPage() != PrintingContext::OK) { OnFailure(); return; } // Actual printing. #if defined(OS_WIN) || defined(OS_MACOSX) document_->RenderPrintedPage(page, printing_context_->context()); #elif defined(OS_POSIX) document_->RenderPrintedPage(page, printing_context_.get()); #endif // Postprocess. if (printing_context_->PageDone() != PrintingContext::OK) { OnFailure(); return; } // Signal everyone that the page is printed. task = new NotificationTask(); task->Init(owner_, JobEventDetails::PAGE_DONE, document_.get(), &page); owner_->message_loop()->PostTask(FROM_HERE, task); } void PrintJobWorker::OnFailure() { DCHECK_EQ(message_loop(), MessageLoop::current()); // We may loose our last reference by broadcasting the FAILED event. scoped_refptr<PrintJobWorkerOwner> handle(owner_); NotificationTask* task = new NotificationTask(); task->Init(owner_, JobEventDetails::FAILED, document_.get(), NULL); owner_->message_loop()->PostTask(FROM_HERE, task); Cancel(); // Makes sure the variables are reinitialized. document_ = NULL; page_number_ = PageNumber::npos(); } } // namespace printing void RunnableMethodTraits<printing::PrintJobWorker>::RetainCallee( printing::PrintJobWorker* obj) { DCHECK(!owner_.get()); owner_ = obj->owner_; } void RunnableMethodTraits<printing::PrintJobWorker>::ReleaseCallee( printing::PrintJobWorker* obj) { DCHECK_EQ(owner_, obj->owner_); owner_ = NULL; }