From d45a698bca6f5b5c7bc17ba338e47502c0085a29 Mon Sep 17 00:00:00 2001 From: Mrunal Kapade Date: Tue, 26 Jul 2016 19:19:22 -0700 Subject: [PATCH] Enable window.print() for Linux This enables window.print() api so that crosswalk based apps can print within their app. It doesn't provide Print Preview like Chrome but does pop up system print dialog to configure print settings. It also relies on a small modification to build/common.gypi in chromium-crosswalk repo to disable the print preview for which I will submit a seprate PR. BUG=XWALK-5680 --- runtime/browser/printing/print_job.cc | 463 ++++++++++++++++ runtime/browser/printing/print_job.h | 231 ++++++++ runtime/browser/printing/print_job_manager.cc | 165 ++++++ runtime/browser/printing/print_job_manager.h | 100 ++++ runtime/browser/printing/print_job_worker.cc | 413 ++++++++++++++ runtime/browser/printing/print_job_worker.h | 164 ++++++ .../printing/print_job_worker_owner.cc | 27 + .../browser/printing/print_job_worker_owner.h | 69 +++ .../printing/print_view_manager_base.cc | 508 ++++++++++++++++++ .../printing/print_view_manager_base.h | 158 ++++++ .../printing/print_view_manager_basic.cc | 29 + .../printing/print_view_manager_basic.h | 30 ++ runtime/browser/printing/printer_query.cc | 131 +++++ runtime/browser/printing/printer_query.h | 100 ++++ .../printing/printing_message_filter.cc | 305 +++++++++++ .../printing/printing_message_filter.h | 117 ++++ runtime/browser/runtime.cc | 2 + .../browser/xwalk_content_browser_client.cc | 4 + runtime/browser/xwalk_runner.cc | 10 + runtime/browser/xwalk_runner.h | 6 + runtime/common/xwalk_notification_types.h | 11 + .../xwalk_print_web_view_helper_delegate.cc | 40 ++ .../xwalk_print_web_view_helper_delegate.h | 28 + .../renderer/xwalk_content_renderer_client.cc | 5 + xwalk.gyp | 25 +- 25 files changed, 3139 insertions(+), 2 deletions(-) create mode 100644 runtime/browser/printing/print_job.cc create mode 100644 runtime/browser/printing/print_job.h create mode 100644 runtime/browser/printing/print_job_manager.cc create mode 100644 runtime/browser/printing/print_job_manager.h create mode 100644 runtime/browser/printing/print_job_worker.cc create mode 100644 runtime/browser/printing/print_job_worker.h create mode 100644 runtime/browser/printing/print_job_worker_owner.cc create mode 100644 runtime/browser/printing/print_job_worker_owner.h create mode 100644 runtime/browser/printing/print_view_manager_base.cc create mode 100644 runtime/browser/printing/print_view_manager_base.h create mode 100644 runtime/browser/printing/print_view_manager_basic.cc create mode 100644 runtime/browser/printing/print_view_manager_basic.h create mode 100644 runtime/browser/printing/printer_query.cc create mode 100644 runtime/browser/printing/printer_query.h create mode 100644 runtime/browser/printing/printing_message_filter.cc create mode 100644 runtime/browser/printing/printing_message_filter.h create mode 100644 runtime/renderer/printing/xwalk_print_web_view_helper_delegate.cc create mode 100644 runtime/renderer/printing/xwalk_print_web_view_helper_delegate.h diff --git a/runtime/browser/printing/print_job.cc b/runtime/browser/printing/print_job.cc new file mode 100644 index 0000000000..4252e93a58 --- /dev/null +++ b/runtime/browser/printing/print_job.cc @@ -0,0 +1,463 @@ +// Copyright (c) 2012 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 "xwalk/runtime/browser/printing/print_job.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/threading/worker_pool.h" +#include "build/build_config.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "printing/printed_document.h" +#include "printing/printed_page.h" +#include "xwalk/runtime/common/xwalk_notification_types.h" +#include "xwalk/runtime/browser/printing/print_job_worker.h" + + +#if defined(OS_WIN) +#include "xwalk/runtime/browser/printing/pdf_to_emf_converter.h" +#include "printing/pdf_render_settings.h" +#endif + +using base::TimeDelta; + +namespace xwalk { + +namespace { + +// Helper function to ensure |owner| is valid until at least |callback| returns. +void HoldRefCallback(const scoped_refptr& owner, + const base::Closure& callback) { + callback.Run(); +} + +} // namespace + +PrintJob::PrintJob() + : source_(NULL), + worker_(), + settings_(), + is_job_pending_(false), + is_canceling_(false), + quit_factory_(this) { + // This is normally a UI message loop, but in unit tests, the message loop is + // of the 'default' type. + DCHECK(base::MessageLoopForUI::IsCurrent() || + base::MessageLoop::current()->type() == + base::MessageLoop::TYPE_DEFAULT); +} + +PrintJob::~PrintJob() { + // The job should be finished (or at least canceled) when it is destroyed. + DCHECK(!is_job_pending_); + DCHECK(!is_canceling_); + DCHECK(!worker_ || !worker_->IsRunning()); + DCHECK(RunsTasksOnCurrentThread()); +} + +void PrintJob::Initialize(PrintJobWorkerOwner* job, + printing::PrintedPagesSource* source, + int page_count) { + DCHECK(!source_); + DCHECK(!worker_.get()); + DCHECK(!is_job_pending_); + DCHECK(!is_canceling_); + DCHECK(!document_.get()); + source_ = source; + worker_.reset(job->DetachWorker(this)); + settings_ = job->settings(); + + printing::PrintedDocument* new_doc = + new printing::PrintedDocument(settings_, + source_, + job->cookie(), + content::BrowserThread::GetBlockingPool()); + new_doc->set_page_count(page_count); + UpdatePrintedDocument(new_doc); + + // Don't forget to register to our own messages. + registrar_.Add(this, NOTIFICATION_XWALK_PRINT_JOB_EVENT, + content::Source(this)); +} + +void PrintJob::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK(RunsTasksOnCurrentThread()); + switch (type) { + case NOTIFICATION_XWALK_PRINT_JOB_EVENT: { + OnNotifyPrintJobEvent(*content::Details(details).ptr()); + break; + } + default: { + break; + } + } +} + +void PrintJob::GetSettingsDone(const printing::PrintSettings& new_settings, + printing::PrintingContext::Result result) { + NOTREACHED(); +} + +PrintJobWorker* PrintJob::DetachWorker(PrintJobWorkerOwner* new_owner) { + NOTREACHED(); + return NULL; +} + +const printing::PrintSettings& PrintJob::settings() const { + return settings_; +} + +int PrintJob::cookie() const { + if (!document_.get()) + // Always use an invalid cookie in this case. + return 0; + return document_->cookie(); +} + +void PrintJob::StartPrinting() { + DCHECK(RunsTasksOnCurrentThread()); + DCHECK(worker_->IsRunning()); + DCHECK(!is_job_pending_); + if (!worker_->IsRunning() || is_job_pending_) + return; + + // Real work is done in PrintJobWorker::StartPrinting(). + worker_->PostTask(FROM_HERE, + base::Bind(&HoldRefCallback, make_scoped_refptr(this), + base::Bind(&PrintJobWorker::StartPrinting, + base::Unretained(worker_.get()), + base::RetainedRef(document_)))); + // Set the flag right now. + is_job_pending_ = true; + + // Tell everyone! + scoped_refptr details( + new JobEventDetails(JobEventDetails::NEW_DOC, document_.get(), NULL)); + content::NotificationService::current()->Notify( + NOTIFICATION_XWALK_PRINT_JOB_EVENT, + content::Source(this), + content::Details(details.get())); +} + +void PrintJob::Stop() { + DCHECK(RunsTasksOnCurrentThread()); + + if (quit_factory_.HasWeakPtrs()) { + // In case we're running a nested message loop to wait for a job to finish, + // and we finished before the timeout, quit the nested loop right away. + Quit(); + quit_factory_.InvalidateWeakPtrs(); + } + + // Be sure to live long enough. + scoped_refptr handle(this); + + if (worker_->IsRunning()) { + ControlledWorkerShutdown(); + } else { + // Flush the cached document. + UpdatePrintedDocument(NULL); + } +} + +void PrintJob::Cancel() { + if (is_canceling_) + return; + is_canceling_ = true; + + // Be sure to live long enough. + scoped_refptr handle(this); + + DCHECK(RunsTasksOnCurrentThread()); + if (worker_ && worker_->IsRunning()) { + // Call this right now so it renders the context invalid. Do not use + // InvokeLater since it would take too much time. + worker_->Cancel(); + } + // Make sure a Cancel() is broadcast. + scoped_refptr details( + new JobEventDetails(JobEventDetails::FAILED, NULL, NULL)); + content::NotificationService::current()->Notify( + NOTIFICATION_XWALK_PRINT_JOB_EVENT, + content::Source(this), + content::Details(details.get())); + Stop(); + is_canceling_ = false; +} + +bool PrintJob::FlushJob(base::TimeDelta timeout) { + // Make sure the object outlive this message loop. + scoped_refptr handle(this); + + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::Bind(&PrintJob::Quit, quit_factory_.GetWeakPtr()), + timeout); + + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); + base::MessageLoop::current()->Run(); + + return true; +} + +void PrintJob::DisconnectSource() { + source_ = NULL; + if (document_.get()) + document_->DisconnectSource(); +} + +bool PrintJob::is_job_pending() const { + return is_job_pending_; +} + +printing::PrintedDocument* PrintJob::document() const { + return document_.get(); +} + +#if defined(OS_WIN) + +class PrintJob::PdfToEmfState { + public: + PdfToEmfState(const gfx::Size& page_size, const gfx::Rect& content_area) + : page_count_(0), + current_page_(0), + pages_in_progress_(0), + page_size_(page_size), + content_area_(content_area), + converter_(PdfToEmfConverter::CreateDefault()) {} + + void Start(const scoped_refptr& data, + const PdfRenderSettings& conversion_settings, + const PdfToEmfConverter::StartCallback& start_callback) { + converter_->Start(data, conversion_settings, start_callback); + } + + void GetMorePages( + const PdfToEmfConverter::GetPageCallback& get_page_callback) { + const int kMaxNumberOfTempFilesPerDocument = 3; + while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument && + current_page_ < page_count_) { + ++pages_in_progress_; + converter_->GetPage(current_page_++, get_page_callback); + } + } + + void OnPageProcessed( + const PdfToEmfConverter::GetPageCallback& get_page_callback) { + --pages_in_progress_; + GetMorePages(get_page_callback); + // Release converter if we don't need this any more. + if (!pages_in_progress_ && current_page_ >= page_count_) + converter_.reset(); + } + + void set_page_count(int page_count) { page_count_ = page_count; } + gfx::Size page_size() const { return page_size_; } + gfx::Rect content_area() const { return content_area_; } + + private: + int page_count_; + int current_page_; + int pages_in_progress_; + gfx::Size page_size_; + gfx::Rect content_area_; + std::unique_ptr converter_; +}; + +void PrintJob::StartPdfToEmfConversion( + const scoped_refptr& bytes, + const gfx::Size& page_size, + const gfx::Rect& content_area) { + DCHECK(!ptd_to_emf_state_.get()); + ptd_to_emf_state_.reset(new PdfToEmfState(page_size, content_area)); + const int kPrinterDpi = settings().dpi(); + ptd_to_emf_state_->Start( + bytes, + printing::PdfRenderSettings(content_area, kPrinterDpi, true), + base::Bind(&PrintJob::OnPdfToEmfStarted, this)); +} + +void PrintJob::OnPdfToEmfStarted(int page_count) { + if (page_count <= 0) { + ptd_to_emf_state_.reset(); + Cancel(); + return; + } + ptd_to_emf_state_->set_page_count(page_count); + ptd_to_emf_state_->GetMorePages( + base::Bind(&PrintJob::OnPdfToEmfPageConverted, this)); +} + +void PrintJob::OnPdfToEmfPageConverted(int page_number, + float scale_factor, + std::unique_ptr emf) { + DCHECK(ptd_to_emf_state_); + if (!document_.get() || !emf) { + ptd_to_emf_state_.reset(); + Cancel(); + return; + } + + // Update the rendered document. It will send notifications to the listener. + document_->SetPage(page_number, std::move(emf), scale_factor, + ptd_to_emf_state_->page_size(), + ptd_to_emf_state_->content_area()); + + ptd_to_emf_state_->GetMorePages( + base::Bind(&PrintJob::OnPdfToEmfPageConverted, this)); +} + +#endif // OS_WIN + +void PrintJob::UpdatePrintedDocument(printing::PrintedDocument* new_document) { + if (document_.get() == new_document) + return; + + document_ = new_document; + + if (document_.get()) { + settings_ = document_->settings(); + } + + if (worker_) { + DCHECK(!is_job_pending_); + // Sync the document with the worker. + worker_->PostTask(FROM_HERE, + base::Bind(&HoldRefCallback, make_scoped_refptr(this), + base::Bind(&PrintJobWorker::OnDocumentChanged, + base::Unretained(worker_.get()), + base::RetainedRef(document_)))); + } +} + +void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::FAILED: { + settings_.Clear(); + // No need to cancel since the worker already canceled itself. + Stop(); + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: { + DCHECK_EQ(event_details.document(), document_.get()); + break; + } + case JobEventDetails::NEW_DOC: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::JOB_DONE: + case JobEventDetails::ALL_PAGES_REQUESTED: { + // Don't care. + break; + } + case JobEventDetails::DOC_DONE: { + // This will call Stop() and broadcast a JOB_DONE message. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this)); + break; + } + case JobEventDetails::PAGE_DONE: +#if defined(OS_WIN) + ptd_to_emf_state_->OnPageProcessed( + base::Bind(&PrintJob::OnPdfToEmfPageConverted, this)); +#endif // OS_WIN + break; + default: { + NOTREACHED(); + break; + } + } +} + +void PrintJob::OnDocumentDone() { + // Be sure to live long enough. The instance could be destroyed by the + // JOB_DONE broadcast. + scoped_refptr handle(this); + + // Stop the worker thread. + Stop(); + + scoped_refptr details( + new JobEventDetails(JobEventDetails::JOB_DONE, document_.get(), NULL)); + content::NotificationService::current()->Notify( + NOTIFICATION_XWALK_PRINT_JOB_EVENT, + content::Source(this), + content::Details(details.get())); +} + +void PrintJob::ControlledWorkerShutdown() { + DCHECK(RunsTasksOnCurrentThread()); + + // The deadlock this code works around is specific to window messaging on + // Windows, so we aren't likely to need it on any other platforms. +#if defined(OS_WIN) + // We could easily get into a deadlock case if worker_->Stop() is used; the + // printer driver created a window as a child of the browser window. By + // canceling the job, the printer driver initiated dialog box is destroyed, + // which sends a blocking message to its parent window. If the browser window + // thread is not processing messages, a deadlock occurs. + // + // This function ensures that the dialog box will be destroyed in a timely + // manner by the mere fact that the thread will terminate. So the potential + // deadlock is eliminated. + worker_->StopSoon(); + + // Delay shutdown until the worker terminates. We want this code path + // to wait on the thread to quit before continuing. + if (worker_->IsRunning()) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&PrintJob::ControlledWorkerShutdown, this), + base::TimeDelta::FromMilliseconds(100)); + return; + } +#endif + + + // Now make sure the thread object is cleaned up. Do this on a worker + // thread because it may block. + base::WorkerPool::PostTaskAndReply( + FROM_HERE, + base::Bind(&PrintJobWorker::Stop, base::Unretained(worker_.get())), + base::Bind(&PrintJob::HoldUntilStopIsCalled, this), + false); + + is_job_pending_ = false; + registrar_.RemoveAll(); + UpdatePrintedDocument(NULL); +} + +void PrintJob::HoldUntilStopIsCalled() { +} + +void PrintJob::Quit() { + base::MessageLoop::current()->QuitWhenIdle(); +} + +// Takes settings_ ownership and will be deleted in the receiving thread. +JobEventDetails::JobEventDetails(Type type, + printing::PrintedDocument* document, + printing::PrintedPage* page) + : document_(document), + page_(page), + type_(type) { +} + +JobEventDetails::~JobEventDetails() { +} + +printing::PrintedDocument* JobEventDetails::document() const { return document_.get(); } + +printing::PrintedPage* JobEventDetails::page() const { return page_.get(); } + +} // namespace xwalk diff --git a/runtime/browser/printing/print_job.h b/runtime/browser/printing/print_job.h new file mode 100644 index 0000000000..1e2395c81c --- /dev/null +++ b/runtime/browser/printing/print_job.h @@ -0,0 +1,231 @@ +// Copyright (c) 2012 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_H_ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_H_ + +#include + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "build/build_config.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "xwalk/runtime/browser/printing/print_job_worker_owner.h" + +class Thread; + +namespace base { +class RefCountedMemory; +} + +namespace printing { +class PrintedDocument; +class PrintedPage; +class PrintedPagesSource; +} + +namespace xwalk { + +class JobEventDetails; +class MetafilePlayer; +class PdfToEmfConverter; +class PrintJobWorker; +class PrinterQuery; + +// Manages the print work for a specific document. Talks to the printer through +// PrintingContext through PrintJobWorker. Hides access to PrintingContext in a +// worker thread so the caller never blocks. PrintJob will send notifications on +// any state change. While printing, the PrintJobManager instance keeps a +// reference to the job to be sure it is kept alive. All the code in this class +// runs in the UI thread. +class PrintJob : public PrintJobWorkerOwner, + public content::NotificationObserver { + public: + // Create a empty PrintJob. When initializing with this constructor, + // post-constructor initialization must be done with Initialize(). + PrintJob(); + + // Grabs the ownership of the PrintJobWorker from another job, which is + // usually a PrinterQuery. Set the expected page count of the print job. + void Initialize(PrintJobWorkerOwner* job, printing::PrintedPagesSource* source, + int page_count); + + // content::NotificationObserver implementation. + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + + // PrintJobWorkerOwner implementation. + void GetSettingsDone(const printing::PrintSettings& new_settings, + printing::PrintingContext::Result result) override; + PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner) override; + const printing::PrintSettings& settings() const override; + int cookie() const override; + + // Starts the actual printing. Signals the worker that it should begin to + // spool as soon as data is available. + void StartPrinting(); + + // Asks for the worker thread to finish its queued tasks and disconnects the + // delegate object. The PrintJobManager will remove its reference. This may + // have the side-effect of destroying the object if the caller doesn't have a + // handle to the object. Use PrintJob::is_stopped() to check whether the + // worker thread has actually stopped. + void Stop(); + + // Cancels printing job and stops the worker thread. Takes effect immediately. + void Cancel(); + + // Synchronously wait for the job to finish. It is mainly useful when the + // process is about to be shut down and we're waiting for the spooler to eat + // our data. + bool FlushJob(base::TimeDelta timeout); + + // Disconnects the PrintedPage source (PrintedPagesSource). It is done when + // the source is being destroyed. + void DisconnectSource(); + + // Returns true if the print job is pending, i.e. between a StartPrinting() + // and the end of the spooling. + bool is_job_pending() const; + + // Access the current printed document. Warning: may be NULL. + printing::PrintedDocument* document() const; + +#if defined(OS_WIN) + void StartPdfToEmfConversion( + const scoped_refptr& bytes, + const gfx::Size& page_size, + const gfx::Rect& content_area); +#endif // OS_WIN + + protected: + ~PrintJob() override; + + private: + // Updates document_ to a new instance. + void UpdatePrintedDocument(printing::PrintedDocument* new_document); + + // Processes a NOTIFY_PRINT_JOB_EVENT notification. + void OnNotifyPrintJobEvent(const JobEventDetails& event_details); + + // Releases the worker thread by calling Stop(), then broadcasts a JOB_DONE + // notification. + void OnDocumentDone(); + + // Terminates the worker thread in a very controlled way, to work around any + // eventual deadlock. + void ControlledWorkerShutdown(); + + // Called at shutdown when running a nested message loop. + void Quit(); + + void HoldUntilStopIsCalled(); + +#if defined(OS_WIN) + void OnPdfToEmfStarted(int page_count); + void OnPdfToEmfPageConverted(int page_number, + float scale_factor, + std::unique_ptr emf); +#endif // OS_WIN + + content::NotificationRegistrar registrar_; + + // Source that generates the PrintedPage's (i.e. a WebContents). It will be + // set back to NULL if the source is deleted before this object. + printing::PrintedPagesSource* source_; + + // All the UI is done in a worker thread because many Win32 print functions + // are blocking and enters a message loop without your consent. There is one + // worker thread per print job. + std::unique_ptr worker_; + + // Cache of the print context settings for access in the UI thread. + printing::PrintSettings settings_; + + // The printed document. + scoped_refptr document_; + + // Is the worker thread printing. + bool is_job_pending_; + + // Is Canceling? If so, try to not cause recursion if on FAILED notification, + // the notified calls Cancel() again. + bool is_canceling_; + +#if defined(OS_WIN) + class PdfToEmfState; + std::unique_ptr ptd_to_emf_state_; +#endif // OS_WIN + + // Used at shutdown so that we can quit a nested message loop. + base::WeakPtrFactory quit_factory_; + + DISALLOW_COPY_AND_ASSIGN(PrintJob); +}; + +// Details for a NOTIFY_PRINT_JOB_EVENT notification. The members may be NULL. +class JobEventDetails : public base::RefCountedThreadSafe { + public: + // Event type. + enum Type { + // Print... dialog box has been closed with OK button. + USER_INIT_DONE, + + // Print... dialog box has been closed with CANCEL button. + USER_INIT_CANCELED, + + // An automated initialization has been done, e.g. Init(false, NULL). + DEFAULT_INIT_DONE, + + // A new document started printing. + NEW_DOC, + + // A new page started printing. + NEW_PAGE, + + // A page is done printing. + PAGE_DONE, + + // A document is done printing. The worker thread is still alive. Warning: + // not a good moment to release the handle to PrintJob. + DOC_DONE, + + // The worker thread is finished. A good moment to release the handle to + // PrintJob. + JOB_DONE, + + // All missing pages have been requested. + ALL_PAGES_REQUESTED, + + // An error occured. Printing is canceled. + FAILED, + }; + + JobEventDetails(Type type, printing::PrintedDocument* document, printing::PrintedPage* page); + + // Getters. + printing::PrintedDocument* document() const; + printing::PrintedPage* page() const; + Type type() const { + return type_; + } + + private: + friend class base::RefCountedThreadSafe; + + ~JobEventDetails(); + + scoped_refptr document_; + scoped_refptr page_; + const Type type_; + + DISALLOW_COPY_AND_ASSIGN(JobEventDetails); +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_H_ diff --git a/runtime/browser/printing/print_job_manager.cc b/runtime/browser/printing/print_job_manager.cc new file mode 100644 index 0000000000..c1b2526383 --- /dev/null +++ b/runtime/browser/printing/print_job_manager.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2012 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 "xwalk/runtime/browser/printing/print_job_manager.h" + +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "printing/printed_document.h" +#include "printing/printed_page.h" +#include "xwalk/runtime/browser/printing/print_job.h" +#include "xwalk/runtime/browser/printing/printer_query.h" +#include "xwalk/runtime/common/xwalk_notification_types.h" + + +namespace xwalk { + +PrintQueriesQueue::PrintQueriesQueue() { +} + +PrintQueriesQueue::~PrintQueriesQueue() { + base::AutoLock lock(lock_); + queued_queries_.clear(); +} + +void PrintQueriesQueue::QueuePrinterQuery(PrinterQuery* job) { + base::AutoLock lock(lock_); + DCHECK(job); + queued_queries_.push_back(make_scoped_refptr(job)); + DCHECK(job->is_valid()); +} + +scoped_refptr PrintQueriesQueue::PopPrinterQuery( + int document_cookie) { + base::AutoLock lock(lock_); + for (PrinterQueries::iterator itr = queued_queries_.begin(); + itr != queued_queries_.end(); ++itr) { + if ((*itr)->cookie() == document_cookie && !(*itr)->is_callback_pending()) { + scoped_refptr current_query(*itr); + queued_queries_.erase(itr); + DCHECK(current_query->is_valid()); + return current_query; + } + } + return NULL; +} + +scoped_refptr PrintQueriesQueue::CreatePrinterQuery( + int render_process_id, + int render_view_id) { + scoped_refptr job = + new PrinterQuery(render_process_id, render_view_id); + return job; +} + +void PrintQueriesQueue::Shutdown() { + PrinterQueries queries_to_stop; + { + base::AutoLock lock(lock_); + queued_queries_.swap(queries_to_stop); + } + // Stop all pending queries, requests to generate print preview do not have + // corresponding PrintJob, so any pending preview requests are not covered + // by PrintJobManager::StopJobs and should be stopped explicitly. + for (PrinterQueries::iterator itr = queries_to_stop.begin(); + itr != queries_to_stop.end(); ++itr) { + (*itr)->PostTask(FROM_HERE, base::Bind(&PrinterQuery::StopWorker, *itr)); + } +} + +PrintJobManager::PrintJobManager() : is_shutdown_(false) { + registrar_.Add(this, NOTIFICATION_XWALK_PRINT_JOB_EVENT, + content::NotificationService::AllSources()); +} + +PrintJobManager::~PrintJobManager() { +} + +scoped_refptr PrintJobManager::queue() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!queue_.get()) + queue_ = new PrintQueriesQueue(); + return queue_; +} + +void PrintJobManager::Shutdown() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(!is_shutdown_); + is_shutdown_ = true; + registrar_.RemoveAll(); + StopJobs(true); + if (queue_.get()) + queue_->Shutdown(); + queue_ = NULL; +} + +void PrintJobManager::StopJobs(bool wait_for_finish) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + // Copy the array since it can be modified in transit. + PrintJobs to_stop; + to_stop.swap(current_jobs_); + + for (PrintJobs::const_iterator job = to_stop.begin(); job != to_stop.end(); + ++job) { + // Wait for two minutes for the print job to be spooled. + if (wait_for_finish) + (*job)->FlushJob(base::TimeDelta::FromMinutes(2)); + (*job)->Stop(); + } +} + +void PrintJobManager::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + switch (type) { + case NOTIFICATION_XWALK_PRINT_JOB_EVENT: { + OnPrintJobEvent(content::Source(source).ptr(), + *content::Details(details).ptr()); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintJobManager::OnPrintJobEvent( + PrintJob* print_job, + const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::NEW_DOC: { + DCHECK(current_jobs_.end() == current_jobs_.find(print_job)); + // Causes a AddRef(). + current_jobs_.insert(print_job); + break; + } + case JobEventDetails::JOB_DONE: { + DCHECK(current_jobs_.end() != current_jobs_.find(print_job)); + current_jobs_.erase(print_job); + break; + } + case JobEventDetails::FAILED: { + current_jobs_.erase(print_job); + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::PAGE_DONE: + case JobEventDetails::DOC_DONE: + case JobEventDetails::ALL_PAGES_REQUESTED: { + // Don't care. + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +} // namespace xwalk diff --git a/runtime/browser/printing/print_job_manager.h b/runtime/browser/printing/print_job_manager.h new file mode 100644 index 0000000000..7cf966976a --- /dev/null +++ b/runtime/browser/printing/print_job_manager.h @@ -0,0 +1,100 @@ +// Copyright (c) 2012 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_MANAGER_H_ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_MANAGER_H_ + +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/threading/non_thread_safe.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +namespace xwalk { + +class JobEventDetails; +class PrintJob; +class PrinterQuery; + +class PrintQueriesQueue : public base::RefCountedThreadSafe { + public: + PrintQueriesQueue(); + + // Queues a semi-initialized worker thread. Can be called from any thread. + // Current use case is queuing from the I/O thread. + // TODO(maruel): Have them vanish after a timeout (~5 minutes?) + void QueuePrinterQuery(PrinterQuery* job); + + // Pops a queued PrintJobWorkerOwner object that was previously queued or + // create new one. Can be called from any thread. + scoped_refptr PopPrinterQuery(int document_cookie); + + // Creates new query. + scoped_refptr CreatePrinterQuery(int render_process_id, + int render_view_id); + + void Shutdown(); + + private: + friend class base::RefCountedThreadSafe; + typedef std::vector > PrinterQueries; + + virtual ~PrintQueriesQueue(); + + // Used to serialize access to queued_workers_. + base::Lock lock_; + + PrinterQueries queued_queries_; + + DISALLOW_COPY_AND_ASSIGN(PrintQueriesQueue); +}; + +class PrintJobManager : public content::NotificationObserver { + public: + PrintJobManager(); + ~PrintJobManager() override; + + // On browser quit, we should wait to have the print job finished. + void Shutdown(); + + // content::NotificationObserver + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + + // Returns queries queue. Never returns NULL. Must be called on Browser UI + // Thread. Reference could be stored and used from any thread. + scoped_refptr queue(); + + private: + typedef std::set > PrintJobs; + + // Processes a NOTIFY_PRINT_JOB_EVENT notification. + void OnPrintJobEvent(PrintJob* print_job, + const JobEventDetails& event_details); + + // Stops all printing jobs. If wait_for_finish is true, tries to give jobs + // a chance to complete before stopping them. + void StopJobs(bool wait_for_finish); + + content::NotificationRegistrar registrar_; + + // Current print jobs that are active. + PrintJobs current_jobs_; + + scoped_refptr queue_; + + bool is_shutdown_; + + DISALLOW_COPY_AND_ASSIGN(PrintJobManager); +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_MANAGER_H_ diff --git a/runtime/browser/printing/print_job_worker.cc b/runtime/browser/printing/print_job_worker.cc new file mode 100644 index 0000000000..f2d9f3f915 --- /dev/null +++ b/runtime/browser/printing/print_job_worker.cc @@ -0,0 +1,413 @@ +// Copyright (c) 2012 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 "xwalk/runtime/browser/printing/print_job_worker.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/values.h" +#include "build/build_config.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "printing/print_job_constants.h" +#include "printing/printed_document.h" +#include "printing/printed_page.h" +#include "printing/printing_utils.h" +#include "ui/base/l10n/l10n_util.h" +#include "xwalk/runtime/browser/printing/print_job.h" +#include "xwalk/runtime/browser/xwalk_content_browser_client.h" +#include "xwalk/runtime/common/xwalk_notification_types.h" + +using content::BrowserThread; + +namespace xwalk { + +namespace { + +// Helper function to ensure |owner| is valid until at least |callback| returns. +void HoldRefCallback(const scoped_refptr& owner, + const base::Closure& callback) { + callback.Run(); +} + +class PrintingContextDelegate : public printing::PrintingContext::Delegate { + public: + PrintingContextDelegate(int render_process_id, int render_view_id); + ~PrintingContextDelegate() override; + + gfx::NativeView GetParentView() override; + std::string GetAppLocale() override; + + // Not exposed to PrintingContext::Delegate because of dependency issues. + content::WebContents* GetWebContents(); + + private: + int render_process_id_; + int render_view_id_; +}; + +PrintingContextDelegate::PrintingContextDelegate(int render_process_id, + int render_view_id) + : render_process_id_(render_process_id), + render_view_id_(render_view_id) { +} + +PrintingContextDelegate::~PrintingContextDelegate() { +} + +gfx::NativeView PrintingContextDelegate::GetParentView() { + content::WebContents* wc = GetWebContents(); + return wc ? wc->GetNativeView() : nullptr; +} + +content::WebContents* PrintingContextDelegate::GetWebContents() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + content::RenderViewHost* view = + content::RenderViewHost::FromID(render_process_id_, render_view_id_); + return view ? content::WebContents::FromRenderViewHost(view) : nullptr; +} + +std::string PrintingContextDelegate::GetAppLocale() { + return XWalkContentBrowserClient::Get()->GetApplicationLocale(); +} + +void NotificationCallback(PrintJobWorkerOwner* print_job, + JobEventDetails::Type detail_type, + printing::PrintedDocument* document, + printing::PrintedPage* page) { + JobEventDetails* details = new JobEventDetails(detail_type, document, page); + content::NotificationService::current()->Notify( + NOTIFICATION_XWALK_PRINT_JOB_EVENT, + // We know that is is a PrintJob object in this circumstance. + content::Source(static_cast(print_job)), + content::Details(details)); +} + +void PostOnOwnerThread(const scoped_refptr& owner, + const printing::PrintingContext::PrintSettingsCallback& callback, + printing::PrintingContext::Result result) { + owner->PostTask(FROM_HERE, base::Bind(&HoldRefCallback, owner, + base::Bind(callback, result))); +} + +} // namespace + +PrintJobWorker::PrintJobWorker(int render_process_id, + int render_view_id, + PrintJobWorkerOwner* owner) + : owner_(owner), thread_("Printing_Worker"), weak_factory_(this) { + // The object is created in the IO thread. + DCHECK(owner_->RunsTasksOnCurrentThread()); + + printing_context_delegate_.reset( + new PrintingContextDelegate(render_process_id, render_view_id)); + printing_context_ = printing::PrintingContext::Create(printing_context_delegate_.get()); +} + +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(owner_->RunsTasksOnCurrentThread()); + Stop(); +} + +void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) { + DCHECK(page_number_ == printing::PageNumber::npos()); + owner_ = new_owner; +} + +void PrintJobWorker::GetSettings( + bool ask_user_for_settings, + int document_page_count, + bool has_selection, + printing::MarginType margin_type, + bool is_scripted) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK_EQ(page_number_, printing::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_margin_type(margin_type); + + // When we delegate to a destination, we don't ask the user for settings. + // TODO(mad): Ask the destination for settings. + if (ask_user_for_settings) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&HoldRefCallback, make_scoped_refptr(owner_), + base::Bind(&PrintJobWorker::GetSettingsWithUI, + base::Unretained(this), + document_page_count, + has_selection, + is_scripted))); + } else { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&HoldRefCallback, make_scoped_refptr(owner_), + base::Bind(&PrintJobWorker::UseDefaultSettings, + base::Unretained(this)))); + } +} + +void PrintJobWorker::SetSettings( + std::unique_ptr new_settings) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&HoldRefCallback, + make_scoped_refptr(owner_), + base::Bind(&PrintJobWorker::UpdatePrintSettings, + base::Unretained(this), + base::Passed(&new_settings)))); +} + +void PrintJobWorker::UpdatePrintSettings( + std::unique_ptr new_settings) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + printing::PrintingContext::Result result = + printing_context_->UpdatePrintSettings(*new_settings); + GetSettingsDone(result); +} + +void PrintJobWorker::GetSettingsDone(printing::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_->PostTask(FROM_HERE, + base::Bind(&PrintJobWorkerOwner::GetSettingsDone, + make_scoped_refptr(owner_), + printing_context_->settings(), + result)); +} + +void PrintJobWorker::GetSettingsWithUI( + int document_page_count, + bool has_selection, + bool is_scripted) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + // weak_factory_ creates pointers valid only on owner_ thread. + printing_context_->AskUserForSettings( + document_page_count, has_selection, is_scripted, + base::Bind(&PostOnOwnerThread, make_scoped_refptr(owner_), + base::Bind(&PrintJobWorker::GetSettingsDone, + weak_factory_.GetWeakPtr()))); +} + +void PrintJobWorker::UseDefaultSettings() { + printing::PrintingContext::Result result = printing_context_->UseDefaultSettings(); + GetSettingsDone(result); +} + +void PrintJobWorker::StartPrinting(printing::PrintedDocument* new_document) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK_EQ(page_number_, printing::PageNumber::npos()); + DCHECK_EQ(document_.get(), new_document); + DCHECK(document_.get()); + + if (!document_.get() || page_number_ != printing::PageNumber::npos() || + document_.get() != new_document) { + return; + } + + base::string16 document_name = + printing::SimplifyDocumentTitle(document_->name()); + printing::PrintingContext::Result result = + printing_context_->NewDocument(document_name); + if (result != printing::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(printing::PrintedDocument* new_document) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK_EQ(page_number_, printing::PageNumber::npos()); + + if (page_number_ != printing::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(task_runner_->RunsTasksOnCurrentThread()); + + if (page_number_ == printing::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_, printing::PageNumber::npos()); + + while (true) { + // Is the page available? + scoped_refptr page = document_->GetPage(page_number_.ToInt()); + if (!page.get()) { + // We need to wait for the page to be available. + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::Bind(&PrintJobWorker::OnNewPage, weak_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(500)); + break; + } + // The page is there, print it. + SpoolPage(page.get()); + ++page_number_; + if (page_number_ == printing::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. +} + +bool PrintJobWorker::IsRunning() const { + return thread_.IsRunning(); +} + +bool PrintJobWorker::PostTask(const tracked_objects::Location& from_here, + const base::Closure& task) { + if (task_runner_.get()) + return task_runner_->PostTask(from_here, task); + return false; +} + +void PrintJobWorker::StopSoon() { + thread_.StopSoon(); +} + +void PrintJobWorker::Stop() { + thread_.Stop(); +} + +bool PrintJobWorker::Start() { + bool result = thread_.Start(); + task_runner_ = thread_.task_runner(); + return result; +} + +void PrintJobWorker::OnDocumentDone() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK_EQ(page_number_, printing::PageNumber::npos()); + DCHECK(document_.get()); + + if (printing_context_->DocumentDone() != printing::PrintingContext::OK) { + OnFailure(); + return; + } + + owner_->PostTask(FROM_HERE, + base::Bind(&NotificationCallback, base::RetainedRef(owner_), + JobEventDetails::DOC_DONE, + base::RetainedRef(document_), nullptr)); + + // Makes sure the variables are reinitialized. + document_ = NULL; +} + +void PrintJobWorker::SpoolPage(printing::PrintedPage* page) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK_NE(page_number_, printing::PageNumber::npos()); + + // Signal everyone that the page is about to be printed. + owner_->PostTask( + FROM_HERE, + base::Bind(&NotificationCallback, base::RetainedRef(owner_), + JobEventDetails::NEW_PAGE, base::RetainedRef(document_), + base::RetainedRef(page))); + + // Preprocess. + if (printing_context_->NewPage() != printing::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() != printing::PrintingContext::OK) { + OnFailure(); + return; + } + + // Signal everyone that the page is printed. + owner_->PostTask( + FROM_HERE, + base::Bind(&NotificationCallback, base::RetainedRef(owner_), + JobEventDetails::PAGE_DONE, base::RetainedRef(document_), + base::RetainedRef(page))); +} + +void PrintJobWorker::OnFailure() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + + // We may loose our last reference by broadcasting the FAILED event. + scoped_refptr handle(owner_); + + owner_->PostTask(FROM_HERE, + base::Bind(&NotificationCallback, base::RetainedRef(owner_), + JobEventDetails::FAILED, + base::RetainedRef(document_), nullptr)); + Cancel(); + + // Makes sure the variables are reinitialized. + document_ = NULL; + page_number_ = printing::PageNumber::npos(); +} + +} // namespace xwalk diff --git a/runtime/browser/printing/print_job_worker.h b/runtime/browser/printing/print_job_worker.h new file mode 100644 index 0000000000..3de876419a --- /dev/null +++ b/runtime/browser/printing/print_job_worker.h @@ -0,0 +1,164 @@ +// Copyright (c) 2012 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_WORKER_H_ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_WORKER_H_ + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread.h" +#include "content/public/browser/browser_thread.h" +#include "printing/page_number.h" +#include "printing/print_job_constants.h" +#include "printing/printing_context.h" +#include "xwalk/runtime/browser/printing/printer_query.h" + +namespace base { +class DictionaryValue; +} + +namespace printing { +class PrintedDocument; +class PrintedPage; +} + +namespace xwalk { + +class PrintJob; +class PrintJobWorkerOwner; + +// Worker thread code. It manages the PrintingContext, which can be blocking +// and/or run a message loop. This is the object that generates most +// NOTIFY_PRINT_JOB_EVENT notifications, but they are generated through a +// NotificationTask task to be executed from the right thread, the UI thread. +// PrintJob always outlives its worker instance. +class PrintJobWorker { + public: + PrintJobWorker(int render_process_id, + int render_view_id, + PrintJobWorkerOwner* owner); + virtual ~PrintJobWorker(); + + void SetNewOwner(PrintJobWorkerOwner* new_owner); + + // Initializes the print settings. If |ask_user_for_settings| is true, a + // Print... dialog box will be shown to ask the user his preference. + // |is_scripted| should be true for calls coming straight from window.print(). + void GetSettings( + bool ask_user_for_settings, + int document_page_count, + bool has_selection, + printing::MarginType margin_type, + bool is_scripted); + + // Set the new print settings. + void SetSettings(std::unique_ptr new_settings); + + // Starts the printing loop. Every pages are printed as soon as the data is + // available. Makes sure the new_document is the right one. + void StartPrinting(printing::PrintedDocument* new_document); + + // Updates the printed document. + void OnDocumentChanged(printing::PrintedDocument* new_document); + + // Dequeues waiting pages. Called when PrintJob receives a + // NOTIFY_PRINTED_DOCUMENT_UPDATED notification. It's time to look again if + // the next page can be printed. + void OnNewPage(); + + // This is the only function that can be called in a thread. + void Cancel(); + + // Returns true if the thread has been started, and not yet stopped. + bool IsRunning() const; + + // Posts the given task to be run. + bool PostTask(const tracked_objects::Location& from_here, + const base::Closure& task); + + // Signals the thread to exit in the near future. + void StopSoon(); + + // Signals the thread to exit and returns once the thread has exited. + void Stop(); + + // Starts the thread. + bool Start(); + + protected: + // Retrieves the context for testing only. + printing::PrintingContext* printing_context() { return printing_context_.get(); } + + private: + // The shared NotificationService service can only be accessed from the UI + // thread, so this class encloses the necessary information to send the + // notification from the right thread. Most NOTIFY_PRINT_JOB_EVENT + // notifications are sent this way, except USER_INIT_DONE, USER_INIT_CANCELED + // and DEFAULT_INIT_DONE. These three are sent through PrintJob::InitDone(). + class NotificationTask; + + // Renders a page in the printer. + void SpoolPage(printing::PrintedPage* page); + + // Closes the job since spooling is done. + void OnDocumentDone(); + + // Discards the current document, the current page and cancels the printing + // context. + void OnFailure(); + + // Asks the user for print settings. Must be called on the UI thread. + // Required on Mac and Linux. Windows can display UI from non-main threads, + // but sticks with this for consistency. + void GetSettingsWithUI( + int document_page_count, + bool has_selection, + bool is_scripted); + + // Called on the UI thread to update the print settings. + void UpdatePrintSettings(std::unique_ptr new_settings); + + // Reports settings back to owner_. + void GetSettingsDone(printing::PrintingContext::Result result); + + // Use the default settings. When using GTK+ or Mac, this can still end up + // displaying a dialog. So this needs to happen from the UI thread on these + // systems. + void UseDefaultSettings(); + + // Printing context delegate. + std::unique_ptr + printing_context_delegate_; + + // Information about the printer setting. + std::unique_ptr printing_context_; + + // The printed document. Only has read-only access. + scoped_refptr document_; + + // The print job owning this worker thread. It is guaranteed to outlive this + // object. + PrintJobWorkerOwner* owner_; + + // Current page number to print. + printing::PageNumber page_number_; + + // Thread to run worker tasks. + base::Thread thread_; + + // Tread-safe pointer to task runner of the |thread_|. + scoped_refptr task_runner_; + + // Used to generate a WeakPtr for callbacks. + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PrintJobWorker); +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_WORKER_H_ diff --git a/runtime/browser/printing/print_job_worker_owner.cc b/runtime/browser/printing/print_job_worker_owner.cc new file mode 100644 index 0000000000..f6f5c2ea51 --- /dev/null +++ b/runtime/browser/printing/print_job_worker_owner.cc @@ -0,0 +1,27 @@ +// Copyright 2014 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 "xwalk/runtime/browser/printing/print_job_worker_owner.h" + +#include "base/message_loop/message_loop.h" + +namespace xwalk { + +PrintJobWorkerOwner::PrintJobWorkerOwner() + : task_runner_(base::MessageLoop::current()->task_runner()) { +} + +PrintJobWorkerOwner::~PrintJobWorkerOwner() { +} + +bool PrintJobWorkerOwner::RunsTasksOnCurrentThread() const { + return task_runner_->RunsTasksOnCurrentThread(); +} + +bool PrintJobWorkerOwner::PostTask(const tracked_objects::Location& from_here, + const base::Closure& task) { + return task_runner_->PostTask(from_here, task); +} + +} // namespace xwalk diff --git a/runtime/browser/printing/print_job_worker_owner.h b/runtime/browser/printing/print_job_worker_owner.h new file mode 100644 index 0000000000..6104d191bd --- /dev/null +++ b/runtime/browser/printing/print_job_worker_owner.h @@ -0,0 +1,69 @@ +// 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_WORKER_OWNER_H__ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_WORKER_OWNER_H__ + +#include "base/memory/ref_counted.h" +#include "printing/printing_context.h" + +namespace base { +class MessageLoop; +class SequencedTaskRunner; +} + +namespace tracked_objects { +class Location; +} + +namespace printing { +class PrintSettings; +} + +namespace xwalk { + +class PrintJobWorker; + +class PrintJobWorkerOwner + : public base::RefCountedThreadSafe { + public: + PrintJobWorkerOwner(); + + // Finishes the initialization began by PrintJobWorker::GetSettings(). + // Creates a new PrintedDocument if necessary. Solely meant to be called by + // PrintJobWorker. + virtual void GetSettingsDone(const printing::PrintSettings& new_settings, + printing::PrintingContext::Result result) = 0; + + // Detach the PrintJobWorker associated to this object. + virtual PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner) = 0; + + // Access the current settings. + virtual const printing::PrintSettings& settings() const = 0; + + // Cookie uniquely identifying the PrintedDocument and/or loaded settings. + virtual int cookie() const = 0; + + // Returns true if the current thread is a thread on which a task + // may be run, and false if no task will be run on the current + // thread. + bool RunsTasksOnCurrentThread() const; + + // Posts the given task to be run. + bool PostTask(const tracked_objects::Location& from_here, + const base::Closure& task); + + protected: + friend class base::RefCountedThreadSafe; + + virtual ~PrintJobWorkerOwner(); + + // Task runner reference. Used to send notifications in the right + // thread. + scoped_refptr task_runner_; +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINT_JOB_WORKER_OWNER_H__ diff --git a/runtime/browser/printing/print_view_manager_base.cc b/runtime/browser/printing/print_view_manager_base.cc new file mode 100644 index 0000000000..c4b2f4d93c --- /dev/null +++ b/runtime/browser/printing/print_view_manager_base.cc @@ -0,0 +1,508 @@ +// Copyright 2013 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 "xwalk/runtime/browser/printing/print_view_manager_base.h" + +#include +#include + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/timer/timer.h" +#include "build/build_config.h" +#include "components/prefs/pref_service.h" +#include "components/printing/common/print_messages.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "printing/pdf_metafile_skia.h" +#include "printing/printed_document.h" +#include "ui/base/l10n/l10n_util.h" +#include "xwalk/runtime/browser/printing/print_job.h" +#include "xwalk/runtime/browser/printing/print_job_manager.h" +#include "xwalk/runtime/browser/printing/printer_query.h" +#include "xwalk/runtime/browser/xwalk_browser_context.h" +#include "xwalk/runtime/browser/xwalk_runner.h" +#include "xwalk/runtime/common/xwalk_notification_types.h" + + +using base::TimeDelta; +using content::BrowserThread; + +namespace xwalk { + +PrintViewManagerBase::PrintViewManagerBase(content::WebContents* web_contents) + : PrintManager(web_contents), + printing_succeeded_(false), + inside_inner_message_loop_(false), +#if !defined(OS_MACOSX) + expecting_first_page_(true), +#endif + queue_(XWalkRunner::GetInstance()->print_job_manager()->queue()) { + DCHECK(queue_.get()); +} + +PrintViewManagerBase::~PrintViewManagerBase() { + ReleasePrinterQuery(); + DisconnectFromCurrentPrintJob(); +} + +#if defined(ENABLE_BASIC_PRINTING) +bool PrintViewManagerBase::PrintNow() { + return PrintNowInternal(new PrintMsg_PrintPages(routing_id())); +} +#endif + +void PrintViewManagerBase::UpdateScriptedPrintingBlocked() { + Send(new PrintMsg_SetScriptedPrintingBlocked( + routing_id(), + false)); +} + +void PrintViewManagerBase::NavigationStopped() { + // Cancel the current job, wait for the worker to finish. + TerminatePrintJob(true); +} + +void PrintViewManagerBase::RenderProcessGone(base::TerminationStatus status) { + PrintManager::RenderProcessGone(status); + ReleasePrinterQuery(); + + if (!print_job_.get()) + return; + + scoped_refptr document(print_job_->document()); + if (document.get()) { + // If IsComplete() returns false, the document isn't completely rendered. + // Since our renderer is gone, there's nothing to do, cancel it. Otherwise, + // the print job may finish without problem. + TerminatePrintJob(!document->IsComplete()); + } +} + +base::string16 PrintViewManagerBase::RenderSourceName() { + base::string16 name(web_contents()->GetTitle()); + // if (name.empty()) + // name = l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE); + return name; +} + +void PrintViewManagerBase::OnDidGetPrintedPagesCount(int cookie, + int number_pages) { + PrintManager::OnDidGetPrintedPagesCount(cookie, number_pages); + OpportunisticallyCreatePrintJob(cookie); +} + +void PrintViewManagerBase::OnDidPrintPage( + const PrintHostMsg_DidPrintPage_Params& params) { + if (!OpportunisticallyCreatePrintJob(params.document_cookie)) + return; + + printing::PrintedDocument* document = print_job_->document(); + if (!document || params.document_cookie != document->cookie()) { + // Out of sync. It may happen since we are completely asynchronous. Old + // spurious messages can be received if one of the processes is overloaded. + return; + } + +#if defined(OS_MACOSX) + const bool metafile_must_be_valid = true; +#else + const bool metafile_must_be_valid = expecting_first_page_; + expecting_first_page_ = false; +#endif + + // Only used when |metafile_must_be_valid| is true. + std::unique_ptr shared_buf; + if (metafile_must_be_valid) { + if (!base::SharedMemory::IsHandleValid(params.metafile_data_handle)) { + NOTREACHED() << "invalid memory handle"; + web_contents()->Stop(); + return; + } + shared_buf.reset(new base::SharedMemory(params.metafile_data_handle, true)); + if (!shared_buf->Map(params.data_size)) { + NOTREACHED() << "couldn't map"; + web_contents()->Stop(); + return; + } + } else { + if (base::SharedMemory::IsHandleValid(params.metafile_data_handle)) { + NOTREACHED() << "unexpected valid memory handle"; + web_contents()->Stop(); + base::SharedMemory::CloseHandle(params.metafile_data_handle); + return; + } + } + + std::unique_ptr + metafile(new printing::PdfMetafileSkia); + if (metafile_must_be_valid) { + if (!metafile->InitFromData(shared_buf->memory(), params.data_size)) { + NOTREACHED() << "Invalid metafile header"; + web_contents()->Stop(); + return; + } + } + +#if defined(OS_WIN) + if (metafile_must_be_valid) { + scoped_refptr bytes = new base::RefCountedBytes( + reinterpret_cast(shared_buf->memory()), + params.data_size); + + document->DebugDumpData(bytes.get(), FILE_PATH_LITERAL(".pdf")); + print_job_->StartPdfToEmfConversion( + bytes, params.page_size, params.content_area); + } +#else + // Update the rendered document. It will send notifications to the listener. + document->SetPage(params.page_number, std::move(metafile), params.page_size, + params.content_area); + + ShouldQuitFromInnerMessageLoop(); +#endif +} + +void PrintViewManagerBase::OnPrintingFailed(int cookie) { + PrintManager::OnPrintingFailed(cookie); + + LOG(ERROR) << "Failed to print."; + + ReleasePrinterQuery(); + + content::NotificationService::current()->Notify( + NOTIFICATION_XWALK_PRINT_JOB_RELEASED, + content::Source(web_contents()), + content::NotificationService::NoDetails()); +} + +void PrintViewManagerBase::OnShowInvalidPrinterSettingsError() { + LOG(ERROR) << "The selected printer is not available or not installed" + " correctly."; +} + +void PrintViewManagerBase::DidStartLoading() { + UpdateScriptedPrintingBlocked(); +} + +bool PrintViewManagerBase::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PrintViewManagerBase, message) + IPC_MESSAGE_HANDLER(PrintHostMsg_DidPrintPage, OnDidPrintPage) + IPC_MESSAGE_HANDLER(PrintHostMsg_ShowInvalidPrinterSettingsError, + OnShowInvalidPrinterSettingsError) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled || PrintManager::OnMessageReceived(message); +} + +void PrintViewManagerBase::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case NOTIFICATION_XWALK_PRINT_JOB_EVENT: { + OnNotifyPrintJobEvent(*content::Details(details).ptr()); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void PrintViewManagerBase::OnNotifyPrintJobEvent( + const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::FAILED: { + TerminatePrintJob(true); + + content::NotificationService::current()->Notify( + NOTIFICATION_XWALK_PRINT_JOB_RELEASED, + content::Source(web_contents()), + content::NotificationService::NoDetails()); + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: { + NOTREACHED(); + break; + } + case JobEventDetails::ALL_PAGES_REQUESTED: { + ShouldQuitFromInnerMessageLoop(); + break; + } + case JobEventDetails::NEW_DOC: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::PAGE_DONE: + case JobEventDetails::DOC_DONE: { + // Don't care about the actual printing process. + break; + } + case JobEventDetails::JOB_DONE: { + // Printing is done, we don't need it anymore. + // print_job_->is_job_pending() may still be true, depending on the order + // of object registration. + printing_succeeded_ = true; + ReleasePrintJob(); + + content::NotificationService::current()->Notify( + NOTIFICATION_XWALK_PRINT_JOB_RELEASED, + content::Source(web_contents()), + content::NotificationService::NoDetails()); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +bool PrintViewManagerBase::RenderAllMissingPagesNow() { + if (!print_job_.get() || !print_job_->is_job_pending()) + return false; + + // We can't print if there is no renderer. + if (!web_contents() || + !web_contents()->GetRenderViewHost() || + !web_contents()->GetRenderViewHost()->IsRenderViewLive()) { + return false; + } + + // Is the document already complete? + if (print_job_->document() && print_job_->document()->IsComplete()) { + printing_succeeded_ = true; + return true; + } + + // WebContents is either dying or a second consecutive request to print + // happened before the first had time to finish. We need to render all the + // pages in an hurry if a print_job_ is still pending. No need to wait for it + // to actually spool the pages, only to have the renderer generate them. Run + // a message loop until we get our signal that the print job is satisfied. + // PrintJob will send a ALL_PAGES_REQUESTED after having received all the + // pages it needs. MessageLoop::current()->QuitWhenIdle() will be called as + // soon as print_job_->document()->IsComplete() is true on either + // ALL_PAGES_REQUESTED or in DidPrintPage(). The check is done in + // ShouldQuitFromInnerMessageLoop(). + // BLOCKS until all the pages are received. (Need to enable recursive task) + if (!RunInnerMessageLoop()) { + // This function is always called from DisconnectFromCurrentPrintJob() so we + // know that the job will be stopped/canceled in any case. + return false; + } + return true; +} + +void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() { + // Look at the reason. + DCHECK(print_job_->document()); + if (print_job_->document() && + print_job_->document()->IsComplete() && + inside_inner_message_loop_) { + // We are in a message loop created by RenderAllMissingPagesNow. Quit from + // it. + base::MessageLoop::current()->QuitWhenIdle(); + inside_inner_message_loop_ = false; + } +} + +bool PrintViewManagerBase::CreateNewPrintJob(PrintJobWorkerOwner* job) { + DCHECK(!inside_inner_message_loop_); + + // Disconnect the current print_job_. + DisconnectFromCurrentPrintJob(); + + // We can't print if there is no renderer. + if (!web_contents()->GetRenderViewHost() || + !web_contents()->GetRenderViewHost()->IsRenderViewLive()) { + return false; + } + + // Ask the renderer to generate the print preview, create the print preview + // view and switch to it, initialize the printer and show the print dialog. + DCHECK(!print_job_.get()); + DCHECK(job); + if (!job) + return false; + + print_job_ = new PrintJob(); + print_job_->Initialize(job, this, number_pages_); + registrar_.Add(this, NOTIFICATION_XWALK_PRINT_JOB_EVENT, + content::Source(print_job_.get())); + printing_succeeded_ = false; + return true; +} + +void PrintViewManagerBase::DisconnectFromCurrentPrintJob() { + // Make sure all the necessary rendered page are done. Don't bother with the + // return value. + bool result = RenderAllMissingPagesNow(); + + // Verify that assertion. + if (print_job_.get() && + print_job_->document() && + !print_job_->document()->IsComplete()) { + DCHECK(!result); + // That failed. + TerminatePrintJob(true); + } else { + // DO NOT wait for the job to finish. + ReleasePrintJob(); + } +#if !defined(OS_MACOSX) + expecting_first_page_ = true; +#endif +} + +void PrintViewManagerBase::PrintingDone(bool success) { + if (!print_job_.get()) + return; + Send(new PrintMsg_PrintingDone(routing_id(), success)); +} + +void PrintViewManagerBase::TerminatePrintJob(bool cancel) { + if (!print_job_.get()) + return; + + if (cancel) { + // We don't need the metafile data anymore because the printing is canceled. + print_job_->Cancel(); + inside_inner_message_loop_ = false; + } else { + DCHECK(!inside_inner_message_loop_); + DCHECK(!print_job_->document() || print_job_->document()->IsComplete()); + + // WebContents is either dying or navigating elsewhere. We need to render + // all the pages in an hurry if a print job is still pending. This does the + // trick since it runs a blocking message loop: + print_job_->Stop(); + } + ReleasePrintJob(); +} + +void PrintViewManagerBase::ReleasePrintJob() { + if (!print_job_.get()) + return; + + PrintingDone(printing_succeeded_); + + registrar_.Remove(this, NOTIFICATION_XWALK_PRINT_JOB_EVENT, + content::Source(print_job_.get())); + print_job_->DisconnectSource(); + // Don't close the worker thread. + print_job_ = NULL; +} + +bool PrintViewManagerBase::RunInnerMessageLoop() { + // This value may actually be too low: + // + // - If we're looping because of printer settings initialization, the premise + // here is that some poor users have their print server away on a VPN over a + // slow connection. In this situation, the simple fact of opening the printer + // can be dead slow. On the other side, we don't want to die infinitely for a + // real network error. Give the printer 60 seconds to comply. + // + // - If we're looping because of renderer page generation, the renderer could + // be CPU bound, the page overly complex/large or the system just + // memory-bound. + static const int kPrinterSettingsTimeout = 60000; + base::OneShotTimer quit_timer; + quit_timer.Start( + FROM_HERE, TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), + base::MessageLoop::current(), &base::MessageLoop::QuitWhenIdle); + + inside_inner_message_loop_ = true; + + // Need to enable recursive task. + { + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); + base::MessageLoop::current()->Run(); + } + + bool success = true; + if (inside_inner_message_loop_) { + // Ok we timed out. That's sad. + inside_inner_message_loop_ = false; + success = false; + } + + return success; +} + +bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) { + if (print_job_.get()) + return true; + + if (!cookie) { + // Out of sync. It may happens since we are completely asynchronous. Old + // spurious message can happen if one of the processes is overloaded. + return false; + } + + // The job was initiated by a script. Time to get the corresponding worker + // thread. + scoped_refptr queued_query = queue_->PopPrinterQuery(cookie); + if (!queued_query.get()) { + NOTREACHED(); + return false; + } + + if (!CreateNewPrintJob(queued_query.get())) { + // Don't kill anything. + return false; + } + + // Settings are already loaded. Go ahead. This will set + // print_job_->is_job_pending() to true. + print_job_->StartPrinting(); + return true; +} + +bool PrintViewManagerBase::PrintNowInternal(IPC::Message* message) { + // Don't print / print preview interstitials or crashed tabs. + if (web_contents()->ShowingInterstitialPage() || + web_contents()->IsCrashed()) { + delete message; + return false; + } + return Send(message); +} + +void PrintViewManagerBase::ReleasePrinterQuery() { + if (!cookie_) + return; + + int cookie = cookie_; + cookie_ = 0; + + PrintJobManager* print_job_manager = + XWalkRunner::GetInstance()->print_job_manager(); + // May be NULL in tests. + if (!print_job_manager) + return; + + scoped_refptr printer_query; + printer_query = queue_->PopPrinterQuery(cookie); + if (!printer_query.get()) + return; + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&PrinterQuery::StopWorker, printer_query.get())); +} + +} // namespace xwalk diff --git a/runtime/browser/printing/print_view_manager_base.h b/runtime/browser/printing/print_view_manager_base.h new file mode 100644 index 0000000000..8e9159e3b0 --- /dev/null +++ b/runtime/browser/printing/print_view_manager_base.h @@ -0,0 +1,158 @@ +// Copyright 2013 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASE_H_ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASE_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string16.h" +#include "build/build_config.h" +#include "components/prefs/pref_member.h" +#include "components/printing/browser/print_manager.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "printing/printed_pages_source.h" + +struct PrintHostMsg_DidPrintPage_Params; + +namespace content { +class RenderViewHost; +} + +namespace xwalk { + +class JobEventDetails; +class MetafilePlayer; +class PrintJob; +class PrintJobWorkerOwner; +class PrintQueriesQueue; + +// Base class for managing the print commands for a WebContents. +class PrintViewManagerBase : public content::NotificationObserver, + public printing::PrintedPagesSource, + public printing::PrintManager { + public: + ~PrintViewManagerBase() override; + +#if defined(ENABLE_BASIC_PRINTING) + // Prints the current document immediately. Since the rendering is + // asynchronous, the actual printing will not be completed on the return of + // this function. Returns false if printing is impossible at the moment. + virtual bool PrintNow(); +#endif // ENABLE_BASIC_PRINTING + + // Whether to block scripted printing or not. + void UpdateScriptedPrintingBlocked(); + + // PrintedPagesSource implementation. + base::string16 RenderSourceName() override; + + protected: + explicit PrintViewManagerBase(content::WebContents* web_contents); + + // Helper method for Print*Now(). + bool PrintNowInternal(IPC::Message* message); + + // Terminates or cancels the print job if one was pending. + void RenderProcessGone(base::TerminationStatus status) override; + + // content::WebContentsObserver implementation. + bool OnMessageReceived(const IPC::Message& message) override; + + private: + // content::NotificationObserver implementation. + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + + // content::WebContentsObserver implementation. + void DidStartLoading() override; + + // Cancels the print job. + void NavigationStopped() override; + + // IPC Message handlers. + void OnDidGetPrintedPagesCount(int cookie, int number_pages) override; + void OnDidPrintPage(const PrintHostMsg_DidPrintPage_Params& params); + void OnPrintingFailed(int cookie) override; + void OnShowInvalidPrinterSettingsError(); + + // Processes a NOTIFY_PRINT_JOB_EVENT notification. + void OnNotifyPrintJobEvent(const JobEventDetails& event_details); + + // Requests the RenderView to render all the missing pages for the print job. + // No-op if no print job is pending. Returns true if at least one page has + // been requested to the renderer. + bool RenderAllMissingPagesNow(); + + // Quits the current message loop if these conditions hold true: a document is + // loaded and is complete and waiting_for_pages_to_be_rendered_ is true. This + // function is called in DidPrintPage() or on ALL_PAGES_REQUESTED + // notification. The inner message loop is created was created by + // RenderAllMissingPagesNow(). + void ShouldQuitFromInnerMessageLoop(); + + // Creates a new empty print job. It has no settings loaded. If there is + // currently a print job, safely disconnect from it. Returns false if it is + // impossible to safely disconnect from the current print job or it is + // impossible to create a new print job. + bool CreateNewPrintJob(PrintJobWorkerOwner* job); + + // Makes sure the current print_job_ has all its data before continuing, and + // disconnect from it. + void DisconnectFromCurrentPrintJob(); + + // Notify that the printing is done. + void PrintingDone(bool success); + + // Terminates the print job. No-op if no print job has been created. If + // |cancel| is true, cancel it instead of waiting for the job to finish. Will + // call ReleasePrintJob(). + void TerminatePrintJob(bool cancel); + + // Releases print_job_. Correctly deregisters from notifications. No-op if + // no print job has been created. + void ReleasePrintJob(); + + // Runs an inner message loop. It will set inside_inner_message_loop_ to true + // while the blocking inner message loop is running. This is useful in cases + // where the RenderView is about to be destroyed while a printing job isn't + // finished. + bool RunInnerMessageLoop(); + + // In the case of Scripted Printing, where the renderer is controlling the + // control flow, print_job_ is initialized whenever possible. No-op is + // print_job_ is initialized. + bool OpportunisticallyCreatePrintJob(int cookie); + + // Release the PrinterQuery associated with our |cookie_|. + void ReleasePrinterQuery(); + + content::NotificationRegistrar registrar_; + + // Manages the low-level talk to the printer. + scoped_refptr print_job_; + + // Indication of success of the print job. + bool printing_succeeded_; + + // Running an inner message loop inside RenderAllMissingPagesNow(). This means + // we are _blocking_ until all the necessary pages have been rendered or the + // print settings are being loaded. + bool inside_inner_message_loop_; + +#if !defined(OS_MACOSX) + // Set to true when OnDidPrintPage() should be expecting the first page. + bool expecting_first_page_; +#endif + + scoped_refptr queue_; + + DISALLOW_COPY_AND_ASSIGN(PrintViewManagerBase); +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASE_H_ diff --git a/runtime/browser/printing/print_view_manager_basic.cc b/runtime/browser/printing/print_view_manager_basic.cc new file mode 100644 index 0000000000..23bb979609 --- /dev/null +++ b/runtime/browser/printing/print_view_manager_basic.cc @@ -0,0 +1,29 @@ +// Copyright 2013 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 "xwalk/runtime/browser/printing/print_view_manager_basic.h" + +#include "build/build_config.h" + +#if defined(OS_ANDROID) +#include "base/bind.h" +#include "printing/printing_context_android.h" +#endif + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(xwalk::PrintViewManagerBasic); + +namespace xwalk { + +PrintViewManagerBasic::PrintViewManagerBasic(content::WebContents* web_contents) + : PrintViewManagerBase(web_contents) { +#if defined(OS_ANDROID) + pdf_writing_done_callback_ = + base::Bind(&printing::PrintingContextAndroid::PdfWritingDone); +#endif +} + +PrintViewManagerBasic::~PrintViewManagerBasic() { +} + +} // namespace xwalk diff --git a/runtime/browser/printing/print_view_manager_basic.h b/runtime/browser/printing/print_view_manager_basic.h new file mode 100644 index 0000000000..1c9f7cc2a4 --- /dev/null +++ b/runtime/browser/printing/print_view_manager_basic.h @@ -0,0 +1,30 @@ +// Copyright 2013 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASIC_H_ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASIC_H_ + +#include "base/macros.h" +#include "content/public/browser/web_contents_user_data.h" +#include "xwalk/runtime/browser/printing/print_view_manager_base.h" + +namespace xwalk { + +// Manages the print commands for a WebContents - basic version. +class PrintViewManagerBasic + : public PrintViewManagerBase, + public content::WebContentsUserData { + public: + ~PrintViewManagerBasic() override; + + private: + explicit PrintViewManagerBasic(content::WebContents* web_contents); + friend class content::WebContentsUserData; + + DISALLOW_COPY_AND_ASSIGN(PrintViewManagerBasic); +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINT_VIEW_MANAGER_BASIC_H_ diff --git a/runtime/browser/printing/printer_query.cc b/runtime/browser/printing/printer_query.cc new file mode 100644 index 0000000000..668a00c9c0 --- /dev/null +++ b/runtime/browser/printing/printer_query.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2012 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 "xwalk/runtime/browser/printing/printer_query.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_restrictions.h" +#include "base/values.h" +#include "xwalk/runtime/browser/printing/print_job_worker.h" + +namespace xwalk { + +PrinterQuery::PrinterQuery(int render_process_id, int render_view_id) + : worker_(new PrintJobWorker(render_process_id, render_view_id, this)), + is_print_dialog_box_shown_(false), + cookie_(printing::PrintSettings::NewCookie()), + last_status_(printing::PrintingContext::FAILED) { + DCHECK(base::MessageLoopForIO::IsCurrent()); +} + +PrinterQuery::~PrinterQuery() { + // The job should be finished (or at least canceled) when it is destroyed. + DCHECK(!is_print_dialog_box_shown_); + // If this fires, it is that this pending printer context has leaked. + DCHECK(!worker_.get()); +} + +void PrinterQuery::GetSettingsDone(const printing::PrintSettings& new_settings, + printing::PrintingContext::Result result) { + is_print_dialog_box_shown_ = false; + last_status_ = result; + if (result != printing::PrintingContext::FAILED) { + settings_ = new_settings; + cookie_ = printing::PrintSettings::NewCookie(); + } else { + // Failure. + cookie_ = 0; + } + + if (!callback_.is_null()) { + // This may cause reentrancy like to call StopWorker(). + callback_.Run(); + callback_.Reset(); + } +} + +PrintJobWorker* PrinterQuery::DetachWorker(PrintJobWorkerOwner* new_owner) { + DCHECK(callback_.is_null()); + DCHECK(worker_.get()); + + worker_->SetNewOwner(new_owner); + return worker_.release(); +} + +const printing::PrintSettings& PrinterQuery::settings() const { + return settings_; +} + +int PrinterQuery::cookie() const { + return cookie_; +} + +void PrinterQuery::GetSettings( + GetSettingsAskParam ask_user_for_settings, + int expected_page_count, + bool has_selection, + printing::MarginType margin_type, + bool is_scripted, + const base::Closure& callback) { + DCHECK(RunsTasksOnCurrentThread()); + DCHECK(!is_print_dialog_box_shown_ || !is_scripted); + + StartWorker(callback); + + // Real work is done in PrintJobWorker::GetSettings(). + is_print_dialog_box_shown_ = + ask_user_for_settings == GetSettingsAskParam::ASK_USER; + worker_->PostTask(FROM_HERE, + base::Bind(&PrintJobWorker::GetSettings, + base::Unretained(worker_.get()), + is_print_dialog_box_shown_, + expected_page_count, + has_selection, + margin_type, + is_scripted)); +} + +void PrinterQuery::SetSettings(std::unique_ptr new_settings, + const base::Closure& callback) { + StartWorker(callback); + + worker_->PostTask(FROM_HERE, + base::Bind(&PrintJobWorker::SetSettings, + base::Unretained(worker_.get()), + base::Passed(&new_settings))); +} + +void PrinterQuery::StartWorker(const base::Closure& callback) { + DCHECK(callback_.is_null()); + DCHECK(worker_.get()); + + // Lazily create the worker thread. There is one worker thread per print job. + if (!worker_->IsRunning()) + worker_->Start(); + + callback_ = callback; +} + +void PrinterQuery::StopWorker() { + if (worker_.get()) { + // http://crbug.com/66082: We're blocking on the PrinterQuery's worker + // thread. It's not clear to me if this may result in blocking the current + // thread for an unacceptable time. We should probably fix it. + base::ThreadRestrictions::ScopedAllowIO allow_io; + worker_->Stop(); + worker_.reset(); + } +} + +bool PrinterQuery::is_callback_pending() const { + return !callback_.is_null(); +} + +bool PrinterQuery::is_valid() const { + return worker_.get() != NULL; +} + +} // namespace xwalk diff --git a/runtime/browser/printing/printer_query.h b/runtime/browser/printing/printer_query.h new file mode 100644 index 0000000000..0768a2769c --- /dev/null +++ b/runtime/browser/printing/printer_query.h @@ -0,0 +1,100 @@ +// Copyright (c) 2012 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINTER_QUERY_H_ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINTER_QUERY_H_ + +#include + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "printing/print_job_constants.h" +#include "xwalk/runtime/browser/printing/print_job_worker_owner.h" + +namespace base { +class DictionaryValue; +} + +namespace xwalk { + +class PrintJobWorker; + +// Query the printer for settings. +class PrinterQuery : public PrintJobWorkerOwner { + public: + // GetSettings() UI parameter. + enum class GetSettingsAskParam { + DEFAULTS, + ASK_USER, + }; + + PrinterQuery(int render_process_id, int render_view_id); + + // PrintJobWorkerOwner implementation. + void GetSettingsDone(const printing::PrintSettings& new_settings, + printing::PrintingContext::Result result) override; + PrintJobWorker* DetachWorker(PrintJobWorkerOwner* new_owner) override; + const printing::PrintSettings& settings() const override; + int cookie() const override; + + // Initializes the printing context. It is fine to call this function multiple + // times to reinitialize the settings. |web_contents_observer| can be queried + // to find the owner of the print setting dialog box. It is unused when + // |ask_for_user_settings| is DEFAULTS. + void GetSettings( + GetSettingsAskParam ask_user_for_settings, + int expected_page_count, + bool has_selection, + printing::MarginType margin_type, + bool is_scripted, + const base::Closure& callback); + + // Updates the current settings with |new_settings| dictionary values. + void SetSettings(std::unique_ptr new_settings, + const base::Closure& callback); + + // Stops the worker thread since the client is done with this object. + void StopWorker(); + + // Returns true if a GetSettings() call is pending completion. + bool is_callback_pending() const; + + printing::PrintingContext::Result last_status() const { return last_status_; } + + // Returns if a worker thread is still associated to this instance. + bool is_valid() const; + + private: + ~PrinterQuery() override; + + // Lazy create the worker thread. There is one worker thread per print job. + void StartWorker(const base::Closure& callback); + + // All the UI is done in a worker thread because many Win32 print functions + // are blocking and enters a message loop without your consent. There is one + // worker thread per print job. + std::unique_ptr worker_; + + // Cache of the print context settings for access in the UI thread. + printing::PrintSettings settings_; + + // Is the Print... dialog box currently shown. + bool is_print_dialog_box_shown_; + + // Cookie that make this instance unique. + int cookie_; + + // Results from the last GetSettingsDone() callback. + printing::PrintingContext::Result last_status_; + + // Callback waiting to be run. + base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(PrinterQuery); +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINTER_QUERY_H_ diff --git a/runtime/browser/printing/printing_message_filter.cc b/runtime/browser/printing/printing_message_filter.cc new file mode 100644 index 0000000000..36008c40f5 --- /dev/null +++ b/runtime/browser/printing/printing_message_filter.cc @@ -0,0 +1,305 @@ +// Copyright (c) 2012 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 "xwalk/runtime/browser/printing/printing_message_filter.h" + +#include +#include + +#include "base/bind.h" +#include "build/build_config.h" +#include "components/printing/browser/print_manager_utils.h" +#include "components/printing/common/print_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/child_process_host.h" +#include "xwalk/runtime/browser/printing/print_job_manager.h" +#include "xwalk/runtime/browser/printing/printer_query.h" +#include "xwalk/runtime/browser/xwalk_runner.h" + +#if defined(OS_ANDROID) +#include "base/strings/string_number_conversions.h" +#include "xwalk/runtime/browser/printing/print_view_manager_basic.h" +#endif + +using content::BrowserThread; + +namespace xwalk { + +namespace { + +#if defined(OS_ANDROID) +content::WebContents* GetWebContentsForRenderView(int render_process_id, + int render_view_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + content::RenderViewHost* view = content::RenderViewHost::FromID( + render_process_id, render_view_id); + return view ? content::WebContents::FromRenderViewHost(view) : nullptr; +} + +PrintViewManagerBasic* GetPrintManager(int render_process_id, + int render_view_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + content::WebContents* web_contents = + GetWebContentsForRenderView(render_process_id, render_view_id); + return web_contents ? PrintViewManagerBasic::FromWebContents(web_contents) + : nullptr; +} +#endif + +} // namespace + +PrintingMessageFilter::PrintingMessageFilter(int render_process_id) + : BrowserMessageFilter(PrintMsgStart), + render_process_id_(render_process_id), + queue_(XWalkRunner::GetInstance()->print_job_manager()->queue()) { + DCHECK(queue_.get()); +} + +PrintingMessageFilter::~PrintingMessageFilter() { +} + +void PrintingMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, BrowserThread::ID* thread) { +#if defined(OS_ANDROID) + if (message.type() == PrintHostMsg_AllocateTempFileForPrinting::ID || + message.type() == PrintHostMsg_TempFileForPrintingWritten::ID) { + *thread = BrowserThread::UI; + } +#endif +} + +bool PrintingMessageFilter::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PrintingMessageFilter, message) +#if defined(OS_ANDROID) + IPC_MESSAGE_HANDLER(PrintHostMsg_AllocateTempFileForPrinting, + OnAllocateTempFileForPrinting) + IPC_MESSAGE_HANDLER(PrintHostMsg_TempFileForPrintingWritten, + OnTempFileForPrintingWritten) +#endif + IPC_MESSAGE_HANDLER(PrintHostMsg_IsPrintingEnabled, OnIsPrintingEnabled) + IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_GetDefaultPrintSettings, + OnGetDefaultPrintSettings) + IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_ScriptedPrint, OnScriptedPrint) + IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_UpdatePrintSettings, + OnUpdatePrintSettings) +#if defined(ENABLE_PRINT_PREVIEW) + IPC_MESSAGE_HANDLER(PrintHostMsg_CheckForCancel, OnCheckForCancel) +#endif + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +#if defined(OS_ANDROID) +void PrintingMessageFilter::OnAllocateTempFileForPrinting( + int render_view_id, + base::FileDescriptor* temp_file_fd, + int* sequence_number) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + PrintViewManagerBasic* print_view_manager = + GetPrintManager(render_process_id_, render_view_id); + if (!print_view_manager) + return; + // The file descriptor is originally created in & passed from the Android + // side, and it will handle the closing. + temp_file_fd->fd = print_view_manager->file_descriptor().fd; + temp_file_fd->auto_close = false; +} + +void PrintingMessageFilter::OnTempFileForPrintingWritten(int render_view_id, + int sequence_number) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + PrintViewManagerBasic* print_view_manager = + GetPrintManager(render_process_id_, render_view_id); + if (print_view_manager) + print_view_manager->PdfWritingDone(true); +} +#endif // defined(OS_ANDROID) + +void PrintingMessageFilter::OnIsPrintingEnabled(bool* is_enabled) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + *is_enabled = true; +} + +void PrintingMessageFilter::OnGetDefaultPrintSettings(IPC::Message* reply_msg) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + scoped_refptr printer_query; + printer_query = queue_->PopPrinterQuery(0); + if (!printer_query.get()) { + printer_query = + queue_->CreatePrinterQuery(render_process_id_, reply_msg->routing_id()); + } + + // Loads default settings. This is asynchronous, only the IPC message sender + // will hang until the settings are retrieved. + printer_query->GetSettings( + PrinterQuery::GetSettingsAskParam::DEFAULTS, + 0, + false, + printing::DEFAULT_MARGINS, + false, + base::Bind(&PrintingMessageFilter::OnGetDefaultPrintSettingsReply, + this, + printer_query, + reply_msg)); +} + +void PrintingMessageFilter::OnGetDefaultPrintSettingsReply( + scoped_refptr printer_query, + IPC::Message* reply_msg) { + PrintMsg_Print_Params params; + if (!printer_query.get() || + printer_query->last_status() != printing::PrintingContext::OK) { + params.Reset(); + } else { + RenderParamsFromPrintSettings(printer_query->settings(), ¶ms); + params.document_cookie = printer_query->cookie(); + } + PrintHostMsg_GetDefaultPrintSettings::WriteReplyParams(reply_msg, params); + Send(reply_msg); + // If printing was enabled. + if (printer_query.get()) { + // If user hasn't cancelled. + if (printer_query->cookie() && printer_query->settings().dpi()) { + queue_->QueuePrinterQuery(printer_query.get()); + } else { + printer_query->StopWorker(); + } + } +} + +void PrintingMessageFilter::OnScriptedPrint( + const PrintHostMsg_ScriptedPrint_Params& params, + IPC::Message* reply_msg) { + scoped_refptr printer_query = + queue_->PopPrinterQuery(params.cookie); + if (!printer_query.get()) { + printer_query = + queue_->CreatePrinterQuery(render_process_id_, reply_msg->routing_id()); + } + printer_query->GetSettings( + PrinterQuery::GetSettingsAskParam::ASK_USER, + params.expected_pages_count, + params.has_selection, + params.margin_type, + params.is_scripted, + base::Bind(&PrintingMessageFilter::OnScriptedPrintReply, + this, + printer_query, + reply_msg)); +} + +void PrintingMessageFilter::OnScriptedPrintReply( + scoped_refptr printer_query, + IPC::Message* reply_msg) { + PrintMsg_PrintPages_Params params; +#if defined(OS_ANDROID) + // We need to save the routing ID here because Send method below deletes the + // |reply_msg| before we can get the routing ID for the Android code. + int routing_id = reply_msg->routing_id(); +#endif + if (printer_query->last_status() != printing::PrintingContext::OK || + !printer_query->settings().dpi()) { + params.Reset(); + } else { + RenderParamsFromPrintSettings(printer_query->settings(), ¶ms.params); + params.params.document_cookie = printer_query->cookie(); + params.pages = printing::PageRange::GetPages(printer_query->settings().ranges()); + } + PrintHostMsg_ScriptedPrint::WriteReplyParams(reply_msg, params); + Send(reply_msg); + if (params.params.dpi && params.params.document_cookie) { +#if defined(OS_ANDROID) + int file_descriptor; + const base::string16& device_name = printer_query->settings().device_name(); + if (base::StringToInt(device_name, &file_descriptor)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&PrintingMessageFilter::UpdateFileDescriptor, this, + routing_id, file_descriptor)); + } +#endif + queue_->QueuePrinterQuery(printer_query.get()); + } else { + printer_query->StopWorker(); + } +} + +#if defined(OS_ANDROID) +void PrintingMessageFilter::UpdateFileDescriptor(int render_view_id, int fd) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + PrintViewManagerBasic* print_view_manager = + GetPrintManager(render_process_id_, render_view_id); + if (print_view_manager) + print_view_manager->set_file_descriptor(base::FileDescriptor(fd, false)); +} +#endif + +void PrintingMessageFilter::OnUpdatePrintSettings( + int document_cookie, const base::DictionaryValue& job_settings, + IPC::Message* reply_msg) { + std::unique_ptr new_settings(job_settings.DeepCopy()); + + scoped_refptr printer_query; + printer_query = queue_->PopPrinterQuery(document_cookie); + if (!printer_query.get()) { + int host_id = render_process_id_; + int routing_id = reply_msg->routing_id(); + if (!new_settings->GetInteger(printing::kPreviewInitiatorHostId, + &host_id) || + !new_settings->GetInteger(printing::kPreviewInitiatorRoutingId, + &routing_id)) { + host_id = content::ChildProcessHost::kInvalidUniqueID; + routing_id = content::ChildProcessHost::kInvalidUniqueID; + } + printer_query = queue_->CreatePrinterQuery(host_id, routing_id); + } + printer_query->SetSettings( + std::move(new_settings), + base::Bind(&PrintingMessageFilter::OnUpdatePrintSettingsReply, this, + printer_query, reply_msg)); +} + +void PrintingMessageFilter::OnUpdatePrintSettingsReply( + scoped_refptr printer_query, + IPC::Message* reply_msg) { + PrintMsg_PrintPages_Params params; + if (!printer_query.get() || + printer_query->last_status() != printing::PrintingContext::OK) { + params.Reset(); + } else { + RenderParamsFromPrintSettings(printer_query->settings(), ¶ms.params); + params.params.document_cookie = printer_query->cookie(); + params.pages = printing::PageRange::GetPages(printer_query->settings().ranges()); + } + PrintHostMsg_UpdatePrintSettings::WriteReplyParams( + reply_msg, + params, + printer_query.get() && + (printer_query->last_status() == printing::PrintingContext::CANCEL)); + Send(reply_msg); + // If user hasn't cancelled. + if (printer_query.get()) { + if (printer_query->cookie() && printer_query->settings().dpi()) { + queue_->QueuePrinterQuery(printer_query.get()); + } else { + printer_query->StopWorker(); + } + } +} + +#if defined(ENABLE_PRINT_PREVIEW) +void PrintingMessageFilter::OnCheckForCancel(int32_t preview_ui_id, + int preview_request_id, + bool* cancel) { + // PrintPreviewUI::GetCurrentPrintPreviewStatus(preview_ui_id, + // preview_request_id, + // cancel); +} +#endif + +} // namespace xwalk diff --git a/runtime/browser/printing/printing_message_filter.h b/runtime/browser/printing/printing_message_filter.h new file mode 100644 index 0000000000..cd1b05c99b --- /dev/null +++ b/runtime/browser/printing/printing_message_filter.h @@ -0,0 +1,117 @@ +// Copyright (c) 2012 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. + +#ifndef XWALK_RUNTIME_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ +#define XWALK_RUNTIME_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ + +#include + +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "components/prefs/pref_member.h" +#include "content/public/browser/browser_message_filter.h" + +#if defined(OS_WIN) +#include "base/memory/shared_memory.h" +#endif + +struct PrintHostMsg_ScriptedPrint_Params; +class Profile; +class ProfileIOData; + +namespace base { +class DictionaryValue; +class FilePath; +} + +namespace content { +class WebContents; +} + +namespace xwalk { + +class PrintJobManager; +class PrintQueriesQueue; +class PrinterQuery; + +// This class filters out incoming printing related IPC messages for the +// renderer process on the IPC thread. +class PrintingMessageFilter : public content::BrowserMessageFilter { + public: + explicit PrintingMessageFilter(int render_process_id); + + // content::BrowserMessageFilter methods. + void OverrideThreadForMessage(const IPC::Message& message, + content::BrowserThread::ID* thread) override; + bool OnMessageReceived(const IPC::Message& message) override; + + private: + ~PrintingMessageFilter() override; + +#if defined(OS_CHROMEOS) || defined(OS_ANDROID) + // Used to ask the browser allocate a temporary file for the renderer + // to fill in resulting PDF in renderer. + void OnAllocateTempFileForPrinting(int render_view_id, + base::FileDescriptor* temp_file_fd, + int* sequence_number); + void OnTempFileForPrintingWritten(int render_view_id, int sequence_number); +#endif + +#if defined(OS_ANDROID) + // Updates the file descriptor for the PrintViewManagerBasic of a given + // render_view_id. + void UpdateFileDescriptor(int render_view_id, int fd); +#endif + + // GetPrintSettingsForRenderView must be called via PostTask and + // base::Bind. Collapse the settings-specific params into a + // struct to avoid running into issues with too many params + // to base::Bind. + struct GetPrintSettingsForRenderViewParams; + + // Checks if printing is enabled. + void OnIsPrintingEnabled(bool* is_enabled); + + // Get the default print setting. + void OnGetDefaultPrintSettings(IPC::Message* reply_msg); + void OnGetDefaultPrintSettingsReply(scoped_refptr printer_query, + IPC::Message* reply_msg); + + // The renderer host have to show to the user the print dialog and returns + // the selected print settings. The task is handled by the print worker + // thread and the UI thread. The reply occurs on the IO thread. + void OnScriptedPrint(const PrintHostMsg_ScriptedPrint_Params& params, + IPC::Message* reply_msg); + void OnScriptedPrintReply(scoped_refptr printer_query, + IPC::Message* reply_msg); + + // Modify the current print settings based on |job_settings|. The task is + // handled by the print worker thread and the UI thread. The reply occurs on + // the IO thread. + void OnUpdatePrintSettings(int document_cookie, + const base::DictionaryValue& job_settings, + IPC::Message* reply_msg); + void OnUpdatePrintSettingsReply(scoped_refptr printer_query, + IPC::Message* reply_msg); + +#if defined(ENABLE_PRINT_PREVIEW) + // Check to see if print preview has been cancelled. + void OnCheckForCancel(int32_t preview_ui_id, + int preview_request_id, + bool* cancel); +#endif + + const int render_process_id_; + + scoped_refptr queue_; + + DISALLOW_COPY_AND_ASSIGN(PrintingMessageFilter); +}; + +} // namespace xwalk + +#endif // XWALK_RUNTIME_BROWSER_PRINTING_PRINTING_MESSAGE_FILTER_H_ diff --git a/runtime/browser/runtime.cc b/runtime/browser/runtime.cc index 5a6fc36e2d..6f72aafb03 100644 --- a/runtime/browser/runtime.cc +++ b/runtime/browser/runtime.cc @@ -32,6 +32,7 @@ #include "xwalk/application/browser/application_system.h" #include "xwalk/runtime/browser/image_util.h" #include "xwalk/runtime/browser/media/media_capture_devices_dispatcher.h" +#include "xwalk/runtime/browser/printing/print_view_manager_basic.h" #include "xwalk/runtime/browser/runtime_file_select_helper.h" #include "xwalk/runtime/browser/ui/color_chooser.h" #include "xwalk/runtime/browser/xwalk_autofill_manager.h" @@ -69,6 +70,7 @@ Runtime::Runtime(content::WebContents* web_contents) observer_(nullptr), weak_ptr_factory_(this) { web_contents_->SetDelegate(this); + PrintViewManagerBasic::CreateForWebContents(web_contents); #if !defined(OS_ANDROID) if (XWalkBrowserContext::GetDefault()->save_form_data()) xwalk_autofill_manager_.reset( diff --git a/runtime/browser/xwalk_content_browser_client.cc b/runtime/browser/xwalk_content_browser_client.cc index 681068d0bf..6e73c9600e 100644 --- a/runtime/browser/xwalk_content_browser_client.cc +++ b/runtime/browser/xwalk_content_browser_client.cc @@ -33,6 +33,7 @@ #include "xwalk/application/common/constants.h" #include "xwalk/runtime/browser/geolocation/xwalk_access_token_store.h" #include "xwalk/runtime/browser/media/media_capture_devices_dispatcher.h" +#include "xwalk/runtime/browser/printing/printing_message_filter.h" #include "xwalk/runtime/browser/renderer_host/pepper/xwalk_browser_pepper_host_factory.h" #include "xwalk/runtime/browser/runtime_platform_util.h" #include "xwalk/runtime/browser/runtime_quota_permission_context.h" @@ -186,6 +187,9 @@ void XWalkContentBrowserClient::RenderProcessWillLaunch( host->AddFilter(new cdm::CdmMessageFilterAndroid()); host->AddFilter(new XWalkRenderMessageFilter(host->GetID())); #endif +#if defined(ENABLE_PRINTING) + host->AddFilter(new PrintingMessageFilter(host->GetID())); +#endif } content::MediaObserver* XWalkContentBrowserClient::GetMediaObserver() { diff --git a/runtime/browser/xwalk_runner.cc b/runtime/browser/xwalk_runner.cc index 3cbe73ed38..1e56ae8a1a 100644 --- a/runtime/browser/xwalk_runner.cc +++ b/runtime/browser/xwalk_runner.cc @@ -17,6 +17,7 @@ #include "xwalk/extensions/common/xwalk_extension_switches.h" #include "xwalk/runtime/browser/application_component.h" #include "xwalk/runtime/browser/devtools/remote_debugging_server.h" +#include "xwalk/runtime/browser/printing/print_job_manager.h" #include "xwalk/runtime/browser/storage_component.h" #include "xwalk/runtime/browser/sysapps_component.h" #include "xwalk/runtime/browser/xwalk_app_extension_bridge.h" @@ -81,12 +82,17 @@ void XWalkRunner::PreMainMessageLoopRun() { app_extension_bridge_->SetApplicationSystem(app_component_->app_system()); browser_context_->set_application_service( app_system()->application_service()); +#if defined(ENABLE_PRINTING) + // Must be created after the NotificationService. + print_job_manager_.reset(new PrintJobManager); +#endif } void XWalkRunner::PostMainMessageLoopRun() { DestroyComponents(); extension_service_.reset(); browser_context_.reset(); + print_job_manager_.reset(); DisableRemoteDebugging(); } @@ -205,6 +211,10 @@ std::unique_ptr XWalkRunner::Create() { #endif } +PrintJobManager* XWalkRunner::print_job_manager() { + return print_job_manager_.get(); +} + content::ContentBrowserClient* XWalkRunner::GetContentBrowserClient() { return content_browser_client_.get(); } diff --git a/runtime/browser/xwalk_runner.h b/runtime/browser/xwalk_runner.h index 01cee5cf69..0c733e9467 100644 --- a/runtime/browser/xwalk_runner.h +++ b/runtime/browser/xwalk_runner.h @@ -23,6 +23,7 @@ class XWalkTestSuiteInitializer; namespace xwalk { class ApplicationComponent; +class PrintJobManager; class RemoteDebuggingServer; class SysAppsComponent; class XWalkBrowserContext; @@ -81,6 +82,8 @@ class XWalkRunner { void EnableRemoteDebugging(int port); void DisableRemoteDebugging(); + PrintJobManager* print_job_manager(); + protected: XWalkRunner(); @@ -148,6 +151,9 @@ class XWalkRunner { // Remote debugger server. std::unique_ptr remote_debugging_server_; + // Ensures that all the print jobs are finished before closing + std::unique_ptr print_job_manager_; + DISALLOW_COPY_AND_ASSIGN(XWalkRunner); }; diff --git a/runtime/common/xwalk_notification_types.h b/runtime/common/xwalk_notification_types.h index f18411f176..15ba473a2f 100644 --- a/runtime/common/xwalk_notification_types.h +++ b/runtime/common/xwalk_notification_types.h @@ -16,6 +16,17 @@ enum NotificationType { // Notify that fullscreen state of a NativeAppWindow is changed. NOTIFICATION_FULLSCREEN_CHANGED = NOTIFICATION_XWALK_START, + // Printing ---------------------------------------------------------------- + + // Notification from PrintJob that an event occurred. It can be that a page + // finished printing or that the print job failed. Details is + // PrintJob::EventDetails. Source is a PrintJob. + NOTIFICATION_XWALK_PRINT_JOB_EVENT, + + // Sent when a PrintJob has been released. + // Source is the WebContents that holds the print job. + NOTIFICATION_XWALK_PRINT_JOB_RELEASED, + NOTIFICATION_XWALK_END, }; diff --git a/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.cc b/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.cc new file mode 100644 index 0000000000..d71f3801f0 --- /dev/null +++ b/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Copyright (c) 2016 Intel Corporation. All rights reserved. + +#include "xwalk/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.h" + +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/render_view.h" +#include "third_party/WebKit/public/web/WebElement.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" + + +XWalkPrintWebViewHelperDelegate::~XWalkPrintWebViewHelperDelegate(){ +} + +bool XWalkPrintWebViewHelperDelegate::CancelPrerender( + content::RenderView* render_view, int routing_id) { + return false; +} + +blink::WebElement XWalkPrintWebViewHelperDelegate::GetPdfElement( + blink::WebLocalFrame* frame) { + return blink::WebElement(); +} + +bool XWalkPrintWebViewHelperDelegate::IsPrintPreviewEnabled() { + return false; +} + +bool XWalkPrintWebViewHelperDelegate::IsAskPrintSettingsEnabled() { + return true; +} + +bool XWalkPrintWebViewHelperDelegate::IsScriptedPrintEnabled() { + return true; +} + +bool XWalkPrintWebViewHelperDelegate::OverridePrint( + blink::WebLocalFrame* frame) { + return false; +} diff --git a/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.h b/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.h new file mode 100644 index 0000000000..2a8aaaf95e --- /dev/null +++ b/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.h @@ -0,0 +1,28 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Copyright (c) 2016 Intel Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef XWALK_RUNTIME_RENDERER_PRINTING_XWALK_PRINT_WEB_VIEW_HELPER_DELEGATE_H_ +#define XWALK_RUNTIME_RENDERER_PRINTING_XWALK_PRINT_WEB_VIEW_HELPER_DELEGATE_H_ + +#include "components/printing/renderer/print_web_view_helper.h" + +class XWalkPrintWebViewHelperDelegate + : public printing::PrintWebViewHelper::Delegate { + public: + ~XWalkPrintWebViewHelperDelegate() override; + + bool CancelPrerender(content::RenderView* render_view, + int routing_id) override; + + blink::WebElement GetPdfElement(blink::WebLocalFrame* frame) override; + + bool IsPrintPreviewEnabled() override; + bool IsAskPrintSettingsEnabled() override; + bool IsScriptedPrintEnabled() override; + + bool OverridePrint(blink::WebLocalFrame* frame) override; +}; // class XWalkPrintWebViewHelperDelegate + +#endif // XWALK_RUNTIME_RENDERER_PRINTING_XWALK_PRINT_WEB_VIEW_HELPER_DELEGATE_H_ diff --git a/runtime/renderer/xwalk_content_renderer_client.cc b/runtime/renderer/xwalk_content_renderer_client.cc index 7475a4f47f..f653927ba9 100644 --- a/runtime/renderer/xwalk_content_renderer_client.cc +++ b/runtime/renderer/xwalk_content_renderer_client.cc @@ -9,6 +9,7 @@ #include "base/strings/utf_string_conversions.h" #include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/content/renderer/password_autofill_agent.h" +#include "components/printing/renderer/print_web_view_helper.h" #include "components/visitedlink/renderer/visitedlink_slave.h" #include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_frame_observer.h" @@ -31,6 +32,7 @@ #include "xwalk/runtime/common/xwalk_localized_error.h" #include "xwalk/runtime/renderer/isolated_file_system.h" #include "xwalk/runtime/renderer/pepper/pepper_helper.h" +#include "xwalk/runtime/renderer/printing/xwalk_print_web_view_helper_delegate.h" #if defined(OS_ANDROID) #include "components/cdm/renderer/android_key_systems.h" @@ -222,6 +224,9 @@ void XWalkContentRendererClient::RenderViewCreated( #if defined(OS_ANDROID) XWalkRenderViewExt::RenderViewCreated(render_view); #endif + new printing::PrintWebViewHelper( + render_view, std::unique_ptr( + new XWalkPrintWebViewHelperDelegate())); } void XWalkContentRendererClient::DidCreateModuleSystem( diff --git a/xwalk.gyp b/xwalk.gyp index 0ff586ba6c..d961a76286 100644 --- a/xwalk.gyp +++ b/xwalk.gyp @@ -29,12 +29,14 @@ '../components/components.gyp:autofill_content_renderer', '../components/components.gyp:autofill_core_browser', '../components/components.gyp:cdm_renderer', - '../components/components_resources.gyp:components_resources', - '../components/components_strings.gyp:components_strings', '../components/components.gyp:devtools_http_handler', + '../components/components.gyp:printing_browser', + '../components/components.gyp:printing_renderer', '../components/components.gyp:user_prefs', '../components/components.gyp:visitedlink_browser', '../components/components.gyp:visitedlink_renderer', + '../components/components_resources.gyp:components_resources', + '../components/components_strings.gyp:components_strings', '../components/url_formatter/url_formatter.gyp:url_formatter', '../content/content.gyp:content', '../content/content.gyp:content_app_both', @@ -49,6 +51,7 @@ '../media/media.gyp:media', '../net/net.gyp:net', '../net/net.gyp:net_resources', + '../printing/printing.gyp:printing', '../skia/skia.gyp:skia', '../storage/storage_browser.gyp:storage', '../storage/storage_common.gyp:storage_common', @@ -173,6 +176,22 @@ 'runtime/browser/image_util.h', 'runtime/browser/media/media_capture_devices_dispatcher.cc', 'runtime/browser/media/media_capture_devices_dispatcher.h', + 'runtime/browser/printing/print_job.cc', + 'runtime/browser/printing/print_job.h', + 'runtime/browser/printing/print_job_manager.cc', + 'runtime/browser/printing/print_job_manager.h', + 'runtime/browser/printing/print_job_worker.cc', + 'runtime/browser/printing/print_job_worker.h', + 'runtime/browser/printing/print_job_worker_owner.cc', + 'runtime/browser/printing/print_job_worker_owner.h', + 'runtime/browser/printing/print_view_manager_base.cc', + 'runtime/browser/printing/print_view_manager_base.h', + 'runtime/browser/printing/print_view_manager_basic.cc', + 'runtime/browser/printing/print_view_manager_basic.h', + 'runtime/browser/printing/printer_query.cc', + 'runtime/browser/printing/printer_query.h', + 'runtime/browser/printing/printing_message_filter.cc', + 'runtime/browser/printing/printing_message_filter.h', 'runtime/browser/renderer_host/pepper/xwalk_browser_pepper_host_factory.cc', 'runtime/browser/renderer_host/pepper/xwalk_browser_pepper_host_factory.h', 'runtime/browser/runtime.cc', @@ -356,6 +375,8 @@ 'runtime/renderer/pepper/pepper_uma_host.h', 'runtime/renderer/pepper/xwalk_renderer_pepper_host_factory.cc', 'runtime/renderer/pepper/xwalk_renderer_pepper_host_factory.h', + 'runtime/renderer/printing/xwalk_print_web_view_helper_delegate.cc', + 'runtime/renderer/printing/xwalk_print_web_view_helper_delegate.h', 'runtime/renderer/xwalk_content_renderer_client.cc', 'runtime/renderer/xwalk_content_renderer_client.h', 'runtime/renderer/xwalk_render_thread_observer_generic.cc',