// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/webui/print_preview_handler.h" #include <string> #include "base/i18n/file_util_icu.h" #include "base/json/json_reader.h" #include "base/path_service.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/printing/print_preview_tab_controller.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/browser/ui/webui/print_preview_ui_html_source.h" #include "chrome/browser/ui/webui/print_preview_ui.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/print_messages.h" #include "content/browser/browser_thread.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/tab_contents/tab_contents.h" #include "printing/backend/print_backend.h" #include "printing/metafile.h" #include "printing/metafile_impl.h" #include "printing/print_job_constants.h" #if defined(OS_POSIX) && !defined(OS_CHROMEOS) #include <cups/cups.h> #include "base/file_util.h" #endif namespace { const bool kColorDefaultValue = false; const bool kLandscapeDefaultValue = false; const char kDisableColorOption[] = "disableColorOption"; const char kSetColorAsDefault[] = "setColorAsDefault"; #if defined(OS_POSIX) && !defined(OS_CHROMEOS) const char kColorDevice[] = "ColorDevice"; #endif TabContents* GetInitiatorTab(TabContents* preview_tab) { printing::PrintPreviewTabController* tab_controller = printing::PrintPreviewTabController::GetInstance(); if (!tab_controller) return NULL; return tab_controller->GetInitiatorTab(preview_tab); } // Get the print job settings dictionary from |args|. The caller takes // ownership of the returned DictionaryValue. Returns NULL on failure. DictionaryValue* GetSettingsDictionary(const ListValue* args) { std::string json_str; if (!args->GetString(0, &json_str)) { NOTREACHED() << "Could not read JSON argument"; return NULL; } if (json_str.empty()) { NOTREACHED() << "Empty print job settings"; return NULL; } scoped_ptr<DictionaryValue> settings(static_cast<DictionaryValue*>( base::JSONReader::Read(json_str, false))); if (!settings.get() || !settings->IsType(Value::TYPE_DICTIONARY)) { NOTREACHED() << "Print job settings must be a dictionary."; return NULL; } if (settings->empty()) { NOTREACHED() << "Print job settings dictionary is empty"; return NULL; } return settings.release(); } } // namespace class PrintSystemTaskProxy : public base::RefCountedThreadSafe<PrintSystemTaskProxy, BrowserThread::DeleteOnUIThread> { public: PrintSystemTaskProxy(const base::WeakPtr<PrintPreviewHandler>& handler, printing::PrintBackend* print_backend) : handler_(handler), print_backend_(print_backend) { } void EnumeratePrinters() { ListValue* printers = new ListValue; int default_printer_index = -1; printing::PrinterList printer_list; print_backend_->EnumeratePrinters(&printer_list); int i = 0; for (printing::PrinterList::iterator index = printer_list.begin(); index != printer_list.end(); ++index, ++i) { printers->Append(new StringValue(index->printer_name)); if (index->is_default) default_printer_index = i; } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &PrintSystemTaskProxy::SendPrinterList, printers, new FundamentalValue(default_printer_index))); } void SendPrinterList(ListValue* printers, FundamentalValue* default_printer_index) { if (handler_) handler_->SendPrinterList(*printers, *default_printer_index); delete printers; delete default_printer_index; } void GetPrinterCapabilities(const std::string& printer_name) { printing::PrinterCapsAndDefaults printer_info; bool supports_color = true; if (!print_backend_->GetPrinterCapsAndDefaults(printer_name, &printer_info)) { return; } #if defined(OS_POSIX) && !defined(OS_CHROMEOS) FilePath ppd_file_path; if (!file_util::CreateTemporaryFile(&ppd_file_path)) return; int data_size = printer_info.printer_capabilities.length(); if (data_size != file_util::WriteFile( ppd_file_path, printer_info.printer_capabilities.data(), data_size)) { file_util::Delete(ppd_file_path, false); return; } ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str()); if (ppd) { ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL); if (attr && attr->value) supports_color = ppd->color_device; ppdClose(ppd); } file_util::Delete(ppd_file_path, false); #elif defined(OS_WIN) || defined(OS_CHROMEOS) NOTIMPLEMENTED(); #endif DictionaryValue settings_info; settings_info.SetBoolean(kDisableColorOption, !supports_color); settings_info.SetBoolean(kSetColorAsDefault, false); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &PrintSystemTaskProxy::SendPrinterCapabilities, settings_info.DeepCopy())); } void SendPrinterCapabilities(DictionaryValue* settings_info) { if (handler_) handler_->SendPrinterCapabilities(*settings_info); delete settings_info; } private: friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; friend class DeleteTask<PrintSystemTaskProxy>; ~PrintSystemTaskProxy() {} base::WeakPtr<PrintPreviewHandler> handler_; scoped_refptr<printing::PrintBackend> print_backend_; DISALLOW_COPY_AND_ASSIGN(PrintSystemTaskProxy); }; // A Task implementation that stores a PDF file on disk. class PrintToPdfTask : public Task { public: // Takes ownership of |metafile|. PrintToPdfTask(printing::Metafile* metafile, const FilePath& path) : metafile_(metafile), path_(path) { } ~PrintToPdfTask() {} // Task implementation virtual void Run() { metafile_->SaveTo(path_); } private: // The metafile holding the PDF data. scoped_ptr<printing::Metafile> metafile_; // The absolute path where the file will be saved. FilePath path_; }; // static FilePath* PrintPreviewHandler::last_saved_path_ = NULL; PrintPreviewHandler::PrintPreviewHandler() : print_backend_(printing::PrintBackend::CreateInstance(NULL)) { } PrintPreviewHandler::~PrintPreviewHandler() { if (select_file_dialog_.get()) select_file_dialog_->ListenerDestroyed(); } void PrintPreviewHandler::RegisterMessages() { web_ui_->RegisterMessageCallback("getPrinters", NewCallback(this, &PrintPreviewHandler::HandleGetPrinters)); web_ui_->RegisterMessageCallback("getPreview", NewCallback(this, &PrintPreviewHandler::HandleGetPreview)); web_ui_->RegisterMessageCallback("print", NewCallback(this, &PrintPreviewHandler::HandlePrint)); web_ui_->RegisterMessageCallback("getPrinterCapabilities", NewCallback(this, &PrintPreviewHandler::HandleGetPrinterCapabilities)); } void PrintPreviewHandler::HandleGetPrinters(const ListValue*) { scoped_refptr<PrintSystemTaskProxy> task = new PrintSystemTaskProxy(AsWeakPtr(), print_backend_.get()); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod(task.get(), &PrintSystemTaskProxy::EnumeratePrinters)); } void PrintPreviewHandler::HandleGetPreview(const ListValue* args) { TabContents* initiator_tab = GetInitiatorTab(web_ui_->tab_contents()); if (!initiator_tab) return; scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args)); if (!settings.get()) return; RenderViewHost* rvh = initiator_tab->render_view_host(); rvh->Send(new PrintMsg_PrintPreview(rvh->routing_id(), *settings)); } void PrintPreviewHandler::HandlePrint(const ListValue* args) { TabContents* initiator_tab = GetInitiatorTab(web_ui_->tab_contents()); if (initiator_tab) { RenderViewHost* rvh = initiator_tab->render_view_host(); rvh->Send(new PrintMsg_ResetScriptedPrintCount(rvh->routing_id())); } scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args)); if (!settings.get()) return; bool print_to_pdf = false; settings->GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf); if (print_to_pdf) { // Pre-populating select file dialog with print job title. TabContentsWrapper* wrapper = TabContentsWrapper::GetCurrentWrapperForContents( web_ui_->tab_contents()); string16 print_job_title_utf16 = wrapper->print_view_manager()->RenderSourceName(); #if defined(OS_WIN) FilePath::StringType print_job_title(print_job_title_utf16); #elif defined(OS_POSIX) FilePath::StringType print_job_title = UTF16ToUTF8(print_job_title_utf16); #endif file_util::ReplaceIllegalCharactersInPath(&print_job_title, '_'); FilePath default_filename(print_job_title); default_filename = default_filename.ReplaceExtension(FILE_PATH_LITERAL("pdf")); SelectFile(default_filename); } else { RenderViewHost* rvh = web_ui_->GetRenderViewHost(); rvh->Send(new PrintMsg_PrintForPrintPreview(rvh->routing_id(), *settings)); } } void PrintPreviewHandler::HandleGetPrinterCapabilities( const ListValue* args) { std::string printer_name; bool ret = args->GetString(0, &printer_name); if (!ret || printer_name.empty()) return; scoped_refptr<PrintSystemTaskProxy> task = new PrintSystemTaskProxy(AsWeakPtr(), print_backend_.get()); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableMethod(task.get(), &PrintSystemTaskProxy::GetPrinterCapabilities, printer_name)); } void PrintPreviewHandler::SendPrinterCapabilities( const DictionaryValue& settings_info) { web_ui_->CallJavascriptFunction("updateWithPrinterCapabilities", settings_info); } void PrintPreviewHandler::SendPrinterList( const ListValue& printers, const FundamentalValue& default_printer_index) { web_ui_->CallJavascriptFunction("setPrinters", printers, default_printer_index); } void PrintPreviewHandler::SelectFile(const FilePath& default_filename) { SelectFileDialog::FileTypeInfo file_type_info; file_type_info.extensions.resize(1); file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("pdf")); // Initializing last_saved_path_ if it is not already initialized. if (!last_saved_path_) { last_saved_path_ = new FilePath(); // Allowing IO operation temporarily. It is ok to do so here because // the select file dialog performs IO anyway in order to display the // folders and also it is modal. base::ThreadRestrictions::ScopedAllowIO allow_io; PathService::Get(chrome::DIR_USER_DOCUMENTS, last_saved_path_); } if (!select_file_dialog_.get()) select_file_dialog_ = SelectFileDialog::Create(this); select_file_dialog_->SelectFile( SelectFileDialog::SELECT_SAVEAS_FILE, string16(), last_saved_path_->Append(default_filename), &file_type_info, 0, FILE_PATH_LITERAL(""), web_ui_->tab_contents(), platform_util::GetTopLevel( web_ui_->tab_contents()->GetNativeView()), NULL); } void PrintPreviewHandler::FileSelected(const FilePath& path, int index, void* params) { PrintPreviewUIHTMLSource::PrintPreviewData data; PrintPreviewUI* pp_ui = reinterpret_cast<PrintPreviewUI*>(web_ui_); pp_ui->html_source()->GetPrintPreviewData(&data); DCHECK(data.first != NULL); DCHECK(data.second > 0); printing::PreviewMetafile* metafile = new printing::PreviewMetafile; metafile->InitFromData(data.first->memory(), data.second); // Updating last_saved_path_ to the newly selected folder. *last_saved_path_ = path.DirName(); PrintToPdfTask* task = new PrintToPdfTask(metafile, path); BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, task); }