diff options
author | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-22 00:01:55 +0000 |
---|---|---|
committer | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-22 00:01:55 +0000 |
commit | 574c7bb72cdc18bc52b69a10aad1287c85a57c70 (patch) | |
tree | b52524b1ad81c3311603d421af4b3587a22320c8 /chrome/service/cloud_print | |
parent | 3ab6796d3645a13a56a2fc311fc36c66d31089d6 (diff) | |
download | chromium_src-574c7bb72cdc18bc52b69a10aad1287c85a57c70.zip chromium_src-574c7bb72cdc18bc52b69a10aad1287c85a57c70.tar.gz chromium_src-574c7bb72cdc18bc52b69a10aad1287c85a57c70.tar.bz2 |
As the first step in an effort to improve robustness of the cloud print proxy, we fetch printer capabilities and defaults in a child process so that printer driver crashes do not crash the entire proxy.
BUG=None
TEST=Registration of printers, printer update in Cloud Print Proxy.
Review URL: http://codereview.chromium.org/5947002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69899 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/service/cloud_print')
-rw-r--r-- | chrome/service/cloud_print/cloud_print_proxy_backend.cc | 148 | ||||
-rw-r--r-- | chrome/service/cloud_print/print_system.h | 13 | ||||
-rw-r--r-- | chrome/service/cloud_print/print_system_cups.cc | 87 | ||||
-rw-r--r-- | chrome/service/cloud_print/print_system_win.cc | 79 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_job_handler.cc | 46 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_job_handler.h | 6 |
6 files changed, 272 insertions, 107 deletions
diff --git a/chrome/service/cloud_print/cloud_print_proxy_backend.cc b/chrome/service/cloud_print/cloud_print_proxy_backend.cc index 1fa5814..61c824e 100644 --- a/chrome/service/cloud_print/cloud_print_proxy_backend.cc +++ b/chrome/service/cloud_print/cloud_print_proxy_backend.cc @@ -136,6 +136,12 @@ class CloudPrintProxyBackend::Core // printer and print them. void InitJobHandlerForPrinter(DictionaryValue* printer_data); + // Callback method for GetPrinterCapsAndDefaults. + void OnReceivePrinterCaps( + bool succeeded, + const std::string& printer_name, + const printing::PrinterCapsAndDefaults& caps_and_defaults); + void HandlePrinterNotification(const std::string& printer_id); void PollForJobs(); // Schedules a task to poll for jobs. Does nothing if a task is already @@ -424,78 +430,96 @@ void CloudPrintProxyBackend::Core::RegisterNextPrinter() { if (next_upload_index_ < printer_list_.size()) { const printing::PrinterBasicInfo& info = printer_list_.at(next_upload_index_); - bool have_printer_info = true; // If we are retrying a previous upload, we don't need to fetch the caps // and defaults again. if (info.printer_name != last_uploaded_printer_name_) { - have_printer_info = - print_system_->GetPrinterCapsAndDefaults( - info.printer_name.c_str(), &last_uploaded_printer_info_); - } - if (have_printer_info) { - last_uploaded_printer_name_ = info.printer_name; - std::string mime_boundary; - CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary); - std::string post_data; - CloudPrintHelpers::AddMultipartValueForUpload(kProxyIdValue, proxy_id_, - mime_boundary, - std::string(), &post_data); - CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue, - info.printer_name, - mime_boundary, - std::string(), &post_data); - CloudPrintHelpers::AddMultipartValueForUpload(kPrinterDescValue, - info.printer_description, - mime_boundary, - std::string() , &post_data); - CloudPrintHelpers::AddMultipartValueForUpload( - kPrinterStatusValue, StringPrintf("%d", info.printer_status), - mime_boundary, std::string(), &post_data); - // Add printer options as tags. - CloudPrintHelpers::GenerateMultipartPostDataForPrinterTags(info.options, - mime_boundary, - &post_data); - - CloudPrintHelpers::AddMultipartValueForUpload( - kPrinterCapsValue, last_uploaded_printer_info_.printer_capabilities, - mime_boundary, last_uploaded_printer_info_.caps_mime_type, - &post_data); - CloudPrintHelpers::AddMultipartValueForUpload( - kPrinterDefaultsValue, last_uploaded_printer_info_.printer_defaults, - mime_boundary, last_uploaded_printer_info_.defaults_mime_type, - &post_data); - // Send a hash of the printer capabilities to the server. We will use this - // later to check if the capabilities have changed - CloudPrintHelpers::AddMultipartValueForUpload( - kPrinterCapsHashValue, - MD5String(last_uploaded_printer_info_.printer_capabilities), - mime_boundary, std::string(), &post_data); - // Terminate the request body - post_data.append("--" + mime_boundary + "--\r\n"); - std::string mime_type("multipart/form-data; boundary="); - mime_type += mime_boundary; - GURL register_url = CloudPrintHelpers::GetUrlForPrinterRegistration( - cloud_print_server_url_); - - next_response_handler_ = - &CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse; - request_ = new CloudPrintURLFetcher; - request_->StartPostRequest(register_url, this, auth_token_, - kCloudPrintAPIMaxRetryCount, mime_type, - post_data); - + cloud_print::PrintSystem::PrinterCapsAndDefaultsCallback* callback = + NewCallback(this, + &CloudPrintProxyBackend::Core::OnReceivePrinterCaps); + // Asnchronously fetch the printer caps and defaults. The story will + // continue in OnReceivePrinterCaps. + print_system_->GetPrinterCapsAndDefaults( + info.printer_name.c_str(), callback); } else { - LOG(ERROR) << "CP_PROXY: Failed to get printer info for: " << - info.printer_name; - next_upload_index_++; - MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, - &CloudPrintProxyBackend::Core::RegisterNextPrinter)); + OnReceivePrinterCaps(true, + last_uploaded_printer_name_, + last_uploaded_printer_info_); } } else { EndRegistration(); } } +void CloudPrintProxyBackend::Core::OnReceivePrinterCaps( + bool succeeded, + const std::string& printer_name, + const printing::PrinterCapsAndDefaults& caps_and_defaults) { + DCHECK(next_upload_index_ < printer_list_.size()); + if (succeeded) { + const printing::PrinterBasicInfo& info = + printer_list_.at(next_upload_index_); + + last_uploaded_printer_name_ = info.printer_name; + last_uploaded_printer_info_ = caps_and_defaults; + + std::string mime_boundary; + CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary); + std::string post_data; + CloudPrintHelpers::AddMultipartValueForUpload(kProxyIdValue, proxy_id_, + mime_boundary, + std::string(), &post_data); + CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue, + info.printer_name, + mime_boundary, + std::string(), &post_data); + CloudPrintHelpers::AddMultipartValueForUpload(kPrinterDescValue, + info.printer_description, + mime_boundary, + std::string() , &post_data); + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterStatusValue, StringPrintf("%d", info.printer_status), + mime_boundary, std::string(), &post_data); + // Add printer options as tags. + CloudPrintHelpers::GenerateMultipartPostDataForPrinterTags(info.options, + mime_boundary, + &post_data); + + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterCapsValue, last_uploaded_printer_info_.printer_capabilities, + mime_boundary, last_uploaded_printer_info_.caps_mime_type, + &post_data); + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterDefaultsValue, last_uploaded_printer_info_.printer_defaults, + mime_boundary, last_uploaded_printer_info_.defaults_mime_type, + &post_data); + // Send a hash of the printer capabilities to the server. We will use this + // later to check if the capabilities have changed + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterCapsHashValue, + MD5String(last_uploaded_printer_info_.printer_capabilities), + mime_boundary, std::string(), &post_data); + // Terminate the request body + post_data.append("--" + mime_boundary + "--\r\n"); + std::string mime_type("multipart/form-data; boundary="); + mime_type += mime_boundary; + GURL register_url = CloudPrintHelpers::GetUrlForPrinterRegistration( + cloud_print_server_url_); + + next_response_handler_ = + &CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse; + request_ = new CloudPrintURLFetcher; + request_->StartPostRequest(register_url, this, auth_token_, + kCloudPrintAPIMaxRetryCount, mime_type, + post_data); + } else { + LOG(ERROR) << "CP_PROXY: Failed to get printer info for: " << + printer_name; + next_upload_index_++; + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, + &CloudPrintProxyBackend::Core::RegisterNextPrinter)); + } +} + void CloudPrintProxyBackend::Core::HandlePrinterNotification( const std::string& printer_id) { DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop()); diff --git a/chrome/service/cloud_print/print_system.h b/chrome/service/cloud_print/print_system.h index 620004c..517a665 100644 --- a/chrome/service/cloud_print/print_system.h +++ b/chrome/service/cloud_print/print_system.h @@ -10,6 +10,7 @@ #include <string> #include <vector> +#include "base/callback.h" #include "base/ref_counted.h" #include "printing/backend/print_backend.h" @@ -130,6 +131,12 @@ class PrintSystem : public base::RefCountedThreadSafe<PrintSystem> { JobSpooler::Delegate* delegate) = 0; }; + typedef Callback3< + bool, + const std::string&, + const printing::PrinterCapsAndDefaults&>::Type + PrinterCapsAndDefaultsCallback; + virtual ~PrintSystem(); // Initialize print system. This need to be called before any other function @@ -139,10 +146,10 @@ class PrintSystem : public base::RefCountedThreadSafe<PrintSystem> { // Enumerates the list of installed local and network printers. virtual void EnumeratePrinters(printing::PrinterList* printer_list) = 0; - // Gets the capabilities and defaults for a specific printer. - virtual bool GetPrinterCapsAndDefaults( + // Gets the capabilities and defaults for a specific printer asynchronously. + virtual void GetPrinterCapsAndDefaults( const std::string& printer_name, - printing::PrinterCapsAndDefaults* printer_info) = 0; + PrinterCapsAndDefaultsCallback* callback) = 0; // Returns true if printer_name points to a valid printer. virtual bool IsValidPrinter(const std::string& printer_name) = 0; diff --git a/chrome/service/cloud_print/print_system_cups.cc b/chrome/service/cloud_print/print_system_cups.cc index 0df7ac6..1a8e7d7 100644 --- a/chrome/service/cloud_print/print_system_cups.cc +++ b/chrome/service/cloud_print/print_system_cups.cc @@ -76,9 +76,9 @@ class PrintSystemCUPS : public PrintSystem { virtual void EnumeratePrinters(printing::PrinterList* printer_list); - virtual bool GetPrinterCapsAndDefaults( + virtual void GetPrinterCapsAndDefaults( const std::string& printer_name, - printing::PrinterCapsAndDefaults* printer_info); + PrinterCapsAndDefaultsCallback* callback); virtual bool IsValidPrinter(const std::string& printer_name); @@ -107,6 +107,11 @@ class PrintSystemCUPS : public PrintSystem { bool ParsePrintTicket(const std::string& print_ticket, std::map<std::string, std::string>* options); + // Synchronous version of GetPrinterCapsAndDefaults. + bool GetPrinterCapsAndDefaults( + const std::string& printer_name, + printing::PrinterCapsAndDefaults* printer_info); + int GetUpdateTimeoutMs() const { return update_timeout_; } @@ -133,6 +138,13 @@ class PrintSystemCUPS : public PrintSystem { PrintServerInfoCUPS* FindServerByFullName( const std::string& full_printer_name, std::string* short_printer_name); + // Helper method to invoke a PrinterCapsAndDefaultsCallback. + static void RunCapsCallback( + PrinterCapsAndDefaultsCallback* callback, + bool succeeded, + const std::string& printer_name, + const printing::PrinterCapsAndDefaults& printer_info); + // PrintServerList contains information about all print servers and backends // this proxy is connected to. typedef std::list<PrintServerInfoCUPS> PrintServerList; @@ -430,31 +442,18 @@ void PrintSystemCUPS::EnumeratePrinters(printing::PrinterList* printer_list) { VLOG(1) << "CUPS: Total " << printer_list->size() << " printers enumerated."; } -bool PrintSystemCUPS::GetPrinterCapsAndDefaults( +void PrintSystemCUPS::GetPrinterCapsAndDefaults( const std::string& printer_name, - printing::PrinterCapsAndDefaults* printer_info) { - DCHECK(initialized_); - std::string short_printer_name; - PrintServerInfoCUPS* server_info = - FindServerByFullName(printer_name, &short_printer_name); - if (!server_info) - return false; - - PrintServerInfoCUPS::CapsMap::iterator caps_it = - server_info->caps_cache.find(printer_name); - if (caps_it != server_info->caps_cache.end()) { - *printer_info = caps_it->second; - return true; - } - - // TODO(gene): Retry multiple times in case of error. - if (!server_info->backend->GetPrinterCapsAndDefaults(short_printer_name, - printer_info) ) { - return false; - } - - server_info->caps_cache[printer_name] = *printer_info; - return true; + PrinterCapsAndDefaultsCallback* callback) { + printing::PrinterCapsAndDefaults printer_info; + bool succeeded = GetPrinterCapsAndDefaults(printer_name, &printer_info); + MessageLoop::current()->PostTask( + FROM_HERE, + NewRunnableFunction(&PrintSystemCUPS::RunCapsCallback, + callback, + succeeded, + printer_name, + printer_info)); } bool PrintSystemCUPS::IsValidPrinter(const std::string& printer_name) { @@ -492,6 +491,33 @@ bool PrintSystemCUPS::ParsePrintTicket(const std::string& print_ticket, return true; } +bool PrintSystemCUPS::GetPrinterCapsAndDefaults( + const std::string& printer_name, + printing::PrinterCapsAndDefaults* printer_info) { + DCHECK(initialized_); + std::string short_printer_name; + PrintServerInfoCUPS* server_info = + FindServerByFullName(printer_name, &short_printer_name); + if (!server_info) + return false; + + PrintServerInfoCUPS::CapsMap::iterator caps_it = + server_info->caps_cache.find(printer_name); + if (caps_it != server_info->caps_cache.end()) { + *printer_info = caps_it->second; + return true; + } + + // TODO(gene): Retry multiple times in case of error. + if (!server_info->backend->GetPrinterCapsAndDefaults(short_printer_name, + printer_info) ) { + return false; + } + + server_info->caps_cache[printer_name] = *printer_info; + return true; +} + bool PrintSystemCUPS::GetJobDetails(const std::string& printer_name, PlatformJobId job_id, PrintJobDetails *job_details) { @@ -737,4 +763,13 @@ PrintServerInfoCUPS* PrintSystemCUPS::FindServerByFullName( return NULL; } +void PrintSystemCUPS::RunCapsCallback( + PrinterCapsAndDefaultsCallback* callback, + bool succeeded, + const std::string& printer_name, + const printing::PrinterCapsAndDefaults& printer_info) { + callback->Run(succeeded, printer_name, printer_info); + delete callback; +} + } // namespace cloud_print diff --git a/chrome/service/cloud_print/print_system_win.cc b/chrome/service/cloud_print/print_system_win.cc index 571b677..4369b54 100644 --- a/chrome/service/cloud_print/print_system_win.cc +++ b/chrome/service/cloud_print/print_system_win.cc @@ -245,9 +245,9 @@ class PrintSystemWin : public PrintSystem { virtual void EnumeratePrinters(printing::PrinterList* printer_list); - virtual bool GetPrinterCapsAndDefaults( + virtual void GetPrinterCapsAndDefaults( const std::string& printer_name, - printing::PrinterCapsAndDefaults* printer_info); + PrinterCapsAndDefaultsCallback* callback); virtual bool IsValidPrinter(const std::string& printer_name); @@ -516,6 +516,69 @@ class PrintSystemWin : public PrintSystem { DISALLOW_COPY_AND_ASSIGN(JobSpoolerWin); }; + // A helper class to handle the response from the utility process to the + // request to fetch printer capabilities and defaults. + class PrinterCapsHandler : public ServiceUtilityProcessHost::Client { + public: + PrinterCapsHandler( + const std::string& printer_name, + PrinterCapsAndDefaultsCallback* callback) + : printer_name_(printer_name), callback_(callback) { + } + virtual void Start() { + g_service_process->io_thread()->message_loop_proxy()->PostTask( + FROM_HERE, + NewRunnableMethod( + this, + &PrinterCapsHandler::GetPrinterCapsAndDefaultsImpl, + base::MessageLoopProxy::CreateForCurrentThread())); + } + + virtual void OnChildDied() { + OnGetPrinterCapsAndDefaultsFailed(printer_name_); + } + virtual void OnGetPrinterCapsAndDefaultsSucceeded( + const std::string& printer_name, + const printing::PrinterCapsAndDefaults& caps_and_defaults) { + callback_->Run(true, printer_name, caps_and_defaults); + callback_.reset(); + Release(); + } + + virtual void OnGetPrinterCapsAndDefaultsFailed( + const std::string& printer_name) { + printing::PrinterCapsAndDefaults caps_and_defaults; + callback_->Run(false, printer_name, caps_and_defaults); + callback_.reset(); + Release(); + } + private: + // Called on the service process IO thread. + void GetPrinterCapsAndDefaultsImpl( + const scoped_refptr<base::MessageLoopProxy>& + client_message_loop_proxy) { + DCHECK(g_service_process->io_thread()->message_loop_proxy()-> + BelongsToCurrentThread()); + scoped_ptr<ServiceUtilityProcessHost> utility_host( + new ServiceUtilityProcessHost(this, client_message_loop_proxy)); + if (utility_host->StartGetPrinterCapsAndDefaults(printer_name_)) { + // The object will self-destruct when the child process dies. + utility_host.release(); + } else { + client_message_loop_proxy->PostTask( + FROM_HERE, + NewRunnableMethod( + this, + &PrinterCapsHandler::OnGetPrinterCapsAndDefaultsFailed, + printer_name_)); + } + } + + std::string printer_name_; + scoped_ptr<PrinterCapsAndDefaultsCallback> callback_; + }; + + virtual PrintSystem::PrintServerWatcher* CreatePrintServerWatcher(); virtual PrintSystem::PrinterWatcher* CreatePrinterWatcher( const std::string& printer_name); @@ -536,10 +599,16 @@ void PrintSystemWin::EnumeratePrinters(printing::PrinterList* printer_list) { print_backend_->EnumeratePrinters(printer_list); } -bool PrintSystemWin::GetPrinterCapsAndDefaults( +void PrintSystemWin::GetPrinterCapsAndDefaults( const std::string& printer_name, - printing::PrinterCapsAndDefaults* printer_info) { - return print_backend_->GetPrinterCapsAndDefaults(printer_name, printer_info); + PrinterCapsAndDefaultsCallback* callback) { + // Launch as child process to retrieve the capabilities and defaults because + // this involves invoking a printer driver DLL and crashes have been known to + // occur. + PrinterCapsHandler* handler = + new PrinterCapsHandler(printer_name, callback); + handler->AddRef(); + handler->Start(); } bool PrintSystemWin::IsValidPrinter(const std::string& printer_name) { diff --git a/chrome/service/cloud_print/printer_job_handler.cc b/chrome/service/cloud_print/printer_job_handler.cc index 5c772ef..6013411 100644 --- a/chrome/service/cloud_print/printer_job_handler.cc +++ b/chrome/service/cloud_print/printer_job_handler.cc @@ -160,31 +160,54 @@ bool PrinterJobHandler::UpdatePrinterInfo() { << printer_info_cloud_.printer_id; // We need to update the parts of the printer info that have changed // (could be printer name, description, status or capabilities). + // First asynchronously fetch the capabilities. printing::PrinterBasicInfo printer_info; printer_watcher_->GetCurrentPrinterInfo(&printer_info); - printing::PrinterCapsAndDefaults printer_caps; + cloud_print::PrintSystem::PrinterCapsAndDefaultsCallback* callback = + NewCallback(this, + &PrinterJobHandler::OnReceivePrinterCaps); + // Asnchronously fetch the printer caps and defaults. The story will + // continue in OnReceivePrinterCaps. + print_system_->GetPrinterCapsAndDefaults( + printer_info.printer_name.c_str(), callback); + // While we are waiting for the data, pretend we have work to do and return + // true. + return true; +} + +void PrinterJobHandler::OnReceivePrinterCaps( + bool succeeded, + const std::string& printer_name, + const printing::PrinterCapsAndDefaults& caps_and_defaults) { + printing::PrinterBasicInfo printer_info; + printer_watcher_->GetCurrentPrinterInfo(&printer_info); + std::string post_data; std::string mime_boundary; CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary); - if (print_system_->GetPrinterCapsAndDefaults( - printer_info.printer_name, &printer_caps)) { - std::string caps_hash = MD5String(printer_caps.printer_capabilities); + + if (succeeded) { + std::string caps_hash = MD5String(caps_and_defaults.printer_capabilities); if (caps_hash != printer_info_cloud_.caps_hash) { // Hashes don't match, we need to upload new capabilities (the defaults // go for free along with the capabilities) printer_info_cloud_.caps_hash = caps_hash; CloudPrintHelpers::AddMultipartValueForUpload( - kPrinterCapsValue, printer_caps.printer_capabilities, - mime_boundary, printer_caps.caps_mime_type, &post_data); + kPrinterCapsValue, caps_and_defaults.printer_capabilities, + mime_boundary, caps_and_defaults.caps_mime_type, &post_data); CloudPrintHelpers::AddMultipartValueForUpload( - kPrinterDefaultsValue, printer_caps.printer_defaults, - mime_boundary, printer_caps.defaults_mime_type, + kPrinterDefaultsValue, caps_and_defaults.printer_defaults, + mime_boundary, caps_and_defaults.defaults_mime_type, &post_data); CloudPrintHelpers::AddMultipartValueForUpload( kPrinterCapsHashValue, caps_hash, mime_boundary, std::string(), &post_data); } + } else { + LOG(ERROR) << "Failed to get printer caps and defaults for printer: " + << printer_name; } + std::string tags_hash = CloudPrintHelpers::GenerateHashOfStringMap(printer_info.options); if (tags_hash != printer_info_cloud_.tags_hash) { @@ -216,7 +239,6 @@ bool PrinterJobHandler::UpdatePrinterInfo() { mime_boundary, std::string(), &post_data); } printer_info_ = printer_info; - bool ret = false; if (!post_data.empty()) { // Terminate the request body post_data.append("--" + mime_boundary + "--\r\n"); @@ -228,9 +250,11 @@ bool PrinterJobHandler::UpdatePrinterInfo() { CloudPrintHelpers::GetUrlForPrinterUpdate( cloud_print_server_url_, printer_info_cloud_.printer_id), this, auth_token_, kCloudPrintAPIMaxRetryCount, mime_type, post_data); - ret = true; + } else { + // We are done here. Go to the Stop state + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop)); } - return ret; } // CloudPrintURLFetcher::Delegate implementation. diff --git a/chrome/service/cloud_print/printer_job_handler.h b/chrome/service/cloud_print/printer_job_handler.h index 9ffc887..f32c1d9 100644 --- a/chrome/service/cloud_print/printer_job_handler.h +++ b/chrome/service/cloud_print/printer_job_handler.h @@ -230,6 +230,12 @@ class PrinterJobHandler : public base::RefCountedThreadSafe<PrinterJobHandler>, bool HavePendingTasks(); void FailedFetchingJobData(); + // Callback that asynchronously receives printer caps and defaults. + void OnReceivePrinterCaps( + bool succeeded, + const std::string& printer_name, + const printing::PrinterCapsAndDefaults& caps_and_defaults); + // Called on print_thread_. void DoPrint(const JobDetails& job_details, const std::string& printer_name); |