blob: ab83c52d3e87601790bae2cea373177caf2d94d3 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/location.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/printing/cups_wrapper.h"
#include "chrome/browser/chromeos/printing/printer_error_codes.h"
#include "chrome/browser/extensions/api/printing/print_job_controller.h"
#include "chrome/browser/extensions/api/printing/print_job_submitter.h"
#include "chrome/browser/extensions/api/printing/printing_api_utils.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/browser/printing/print_preview_sticky_settings.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chromeos/crosapi/mojom/local_printer.mojom.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/printing/common/cloud_print_cdd_conversion.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "printing/backend/print_backend.h"
#include "printing/print_settings.h"
#include "printing/printed_document.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/local_printer_ash.h"
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/printing/print_job_utils_lacros.h"
#include "chromeos/lacros/lacros_service.h"
#endif
namespace extensions {
namespace {
constexpr char kInvalidPrinterIdError[] = "Invalid printer ID";
constexpr char kNoActivePrintJobWithIdError[] =
"No active print job with given ID";
} // namespace
// static
std::unique_ptr<PrintingAPIHandler> PrintingAPIHandler::CreateForTesting(
content::BrowserContext* browser_context,
EventRouter* event_router,
ExtensionRegistry* extension_registry,
std::unique_ptr<PrintJobController> print_job_controller,
std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
crosapi::mojom::LocalPrinter* local_printer) {
auto handler = std::make_unique<PrintingAPIHandler>(
browser_context, event_router, extension_registry,
std::move(print_job_controller), std::move(cups_wrapper), local_printer);
#if BUILDFLAG(IS_CHROMEOS_LACROS)
handler->local_printer_version_ = crosapi::mojom::LocalPrinter::Version_;
#endif
return handler;
}
PrintingAPIHandler::PrintingAPIHandler(content::BrowserContext* browser_context)
: PrintingAPIHandler(browser_context,
EventRouter::Get(browser_context),
ExtensionRegistry::Get(browser_context),
PrintJobController::Create(),
chromeos::CupsWrapper::Create()) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
DCHECK(crosapi::CrosapiManager::IsInitialized());
local_printer_ =
crosapi::CrosapiManager::Get()->crosapi_ash()->local_printer_ash();
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
chromeos::LacrosService* service = chromeos::LacrosService::Get();
if (!service->IsAvailable<crosapi::mojom::LocalPrinter>()) {
LOG(ERROR) << "Local printer not available (PrintJobControllerImpl)";
return;
}
local_printer_ = service->GetRemote<crosapi::mojom::LocalPrinter>().get();
local_printer_version_ =
service->GetInterfaceVersion(crosapi::mojom::LocalPrinter::Uuid_);
if (local_printer_version_ <
int{crosapi::mojom::LocalPrinter::MethodMinVersions::
kAddPrintJobObserverMinVersion}) {
LOG(WARNING) << "Ash LocalPrinter version " << local_printer_version_
<< " does not support AddExtensionPrintJobObserver().";
return;
}
#endif
local_printer_->AddPrintJobObserver(
receiver_.BindNewPipeAndPassRemoteWithVersion(),
crosapi::mojom::PrintJobSource::kExtension, base::DoNothing());
}
PrintingAPIHandler::PrintingAPIHandler(
content::BrowserContext* browser_context,
EventRouter* event_router,
ExtensionRegistry* extension_registry,
std::unique_ptr<PrintJobController> print_job_controller,
std::unique_ptr<chromeos::CupsWrapper> cups_wrapper,
crosapi::mojom::LocalPrinter* local_printer)
: browser_context_(browser_context),
event_router_(event_router),
extension_registry_(extension_registry),
print_job_controller_(std::move(print_job_controller)),
cups_wrapper_(std::move(cups_wrapper)),
local_printer_(local_printer) {}
PrintingAPIHandler::~PrintingAPIHandler() = default;
// static
std::string PrintingAPIHandler::CreateUniqueId(const std::string& printer_id,
int job_id) {
return base::StringPrintf("%s%d", printer_id.c_str(), job_id);
}
// static
BrowserContextKeyedAPIFactory<PrintingAPIHandler>*
PrintingAPIHandler::GetFactoryInstance() {
static base::NoDestructor<BrowserContextKeyedAPIFactory<PrintingAPIHandler>>
instance;
return instance.get();
}
// static
PrintingAPIHandler* PrintingAPIHandler::Get(
content::BrowserContext* browser_context) {
return BrowserContextKeyedAPIFactory<PrintingAPIHandler>::Get(
browser_context);
}
// static
void PrintingAPIHandler::RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterListPref(prefs::kPrintingAPIExtensionsAllowlist);
}
void PrintingAPIHandler::SubmitJob(
gfx::NativeWindow native_window,
scoped_refptr<const extensions::Extension> extension,
std::unique_ptr<api::printing::SubmitJob::Params> params,
SubmitJobCallback callback) {
// PrintingAPIHandler must outlive PrintJobSubmitter. Even if the WeakPtr
// expires, PrintJobSubmitter will continue to access PrintingAPIHandler
// member variables.
PrintJobSubmitter::Run(std::make_unique<PrintJobSubmitter>(
native_window, browser_context_, print_job_controller_.get(),
&pdf_flattener_, std::move(extension), std::move(params->request),
#if BUILDFLAG(IS_CHROMEOS_LACROS)
local_printer_version_,
#endif
local_printer_,
base::BindOnce(&PrintingAPIHandler::OnPrintJobSubmitted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
}
void PrintingAPIHandler::OnPrintJobSubmitted(
SubmitJobCallback callback,
absl::optional<int> job_id,
printing::PrintJob* print_job,
printing::PrintedDocument* document,
absl::optional<std::string> error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!(job_id && error));
if (!job_id) {
absl::optional<api::printing::SubmitJobStatus> status;
if (!error)
status = api::printing::SUBMIT_JOB_STATUS_USER_REJECTED;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), status, absl::nullopt,
std::move(error)));
return;
}
DCHECK_EQ(print_job->source(), crosapi::mojom::PrintJob::Source::EXTENSION);
std::string printer_id =
base::UTF16ToUTF8(document->settings().device_name());
DCHECK(!printer_id.empty());
std::string cups_id = CreateUniqueId(printer_id, *job_id);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), api::printing::SUBMIT_JOB_STATUS_OK,
cups_id, absl::nullopt));
DCHECK(!base::Contains(print_jobs_, cups_id));
print_jobs_[cups_id] =
PrintJobInfo{printer_id, *job_id, print_job->source_id()};
#if BUILDFLAG(IS_CHROMEOS_LACROS)
NotifyAshJobCreated(*print_job, *job_id, *document, local_printer_);
#endif
if (!extension_registry_->enabled_extensions().Contains(
print_job->source_id())) {
return;
}
auto event =
std::make_unique<Event>(events::PRINTING_ON_JOB_STATUS_CHANGED,
api::printing::OnJobStatusChanged::kEventName,
api::printing::OnJobStatusChanged::Create(
cups_id, api::printing::JOB_STATUS_PENDING));
event_router_->DispatchEventToExtension(print_job->source_id(),
std::move(event));
}
absl::optional<std::string> PrintingAPIHandler::CancelJob(
const std::string& extension_id,
const std::string& job_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto it = print_jobs_.find(job_id);
// If there was no print job with specified id sent by the extension return
// an error.
if (it == print_jobs_.end() || it->second.extension_id != extension_id) {
return kNoActivePrintJobWithIdError;
}
if (!local_printer_)
return "Local printer not available";
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (local_printer_version_ <
int{crosapi::mojom::LocalPrinter::MethodMinVersions::
kCancelPrintJobMinVersion}) {
LOG(WARNING) << "Ash LocalPrinter version " << local_printer_version_
<< " does not support CancelPrintJob().";
return "Ash local printer interface does not support CancelPrintJob()";
}
#endif
local_printer_->CancelPrintJob(it->second.printer_id, it->second.job_id,
base::DoNothing());
return absl::nullopt;
}
void PrintingAPIHandler::GetPrinters(GetPrintersCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!local_printer_) {
LOG(ERROR) << "Local printer interface not available "
"(PrintingAPIHandler::GetPrinters()";
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
std::vector<api::printing::Printer>{}));
return;
}
local_printer_->GetPrinters(
base::BindOnce(&PrintingAPIHandler::OnPrintersRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void PrintingAPIHandler::OnPrintersRetrieved(
GetPrintersCallback callback,
std::vector<crosapi::mojom::LocalDestinationInfoPtr> data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PrefService* prefs =
Profile::FromBrowserContext(browser_context_)->GetPrefs();
absl::optional<DefaultPrinterRules> default_printer_rules =
GetDefaultPrinterRules(prefs->GetString(
prefs::kPrintPreviewDefaultDestinationSelectionRules));
auto* sticky_settings = printing::PrintPreviewStickySettings::GetInstance();
sticky_settings->RestoreFromPrefs(prefs);
base::flat_map<std::string, int> recently_used_ranks =
sticky_settings->GetPrinterRecentlyUsedRanks();
std::vector<api::printing::Printer> printers;
printers.reserve(data.size());
for (const crosapi::mojom::LocalDestinationInfoPtr& ptr : data) {
printers.push_back(
PrinterToIdl(*ptr, default_printer_rules, recently_used_ranks));
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(printers)));
}
void PrintingAPIHandler::GetPrinterInfo(const std::string& printer_id,
GetPrinterInfoCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!local_printer_) {
LOG(ERROR)
<< "Local printer not available (PrintingAPIHandler::GetPrinterInfo)";
OnPrinterCapabilitiesRetrieved(std::string(), std::move(callback), nullptr);
return;
}
local_printer_->GetCapability(
printer_id,
base::BindOnce(&PrintingAPIHandler::OnPrinterCapabilitiesRetrieved,
weak_ptr_factory_.GetWeakPtr(), printer_id,
std::move(callback)));
}
void PrintingAPIHandler::OnPrinterCapabilitiesRetrieved(
const std::string& printer_id,
GetPrinterInfoCallback callback,
crosapi::mojom::CapabilitiesResponsePtr caps) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!caps) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), /*capabilities=*/absl::nullopt,
/*status=*/absl::nullopt, kInvalidPrinterIdError));
return;
}
if (!caps->capabilities) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), /*capabilities=*/absl::nullopt,
/*status=*/api::printing::PRINTER_STATUS_UNREACHABLE,
/*error=*/absl::nullopt));
return;
}
cups_wrapper_->QueryCupsPrinterStatus(
printer_id,
base::BindOnce(&PrintingAPIHandler::OnPrinterStatusRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
cloud_print::PrinterSemanticCapsAndDefaultsToCdd(
*caps->capabilities)));
}
void PrintingAPIHandler::OnPrinterStatusRetrieved(
GetPrinterInfoCallback callback,
base::Value capabilities,
std::unique_ptr<::printing::PrinterStatus> printer_status) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!printer_status) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(capabilities),
api::printing::PRINTER_STATUS_UNREACHABLE,
/*error=*/absl::nullopt));
return;
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback), std::move(capabilities),
PrinterStatusToIdl(chromeos::PrinterErrorCodeFromPrinterStatusReasons(
*printer_status)),
/*error=*/absl::nullopt));
}
void PrintingAPIHandler::SetPrintJobControllerForTesting(
std::unique_ptr<PrintJobController> print_job_controller) {
print_job_controller_ = std::move(print_job_controller);
}
void PrintingAPIHandler::OnPrintJobUpdate(
const std::string& printer_id,
unsigned int job_id,
crosapi::mojom::PrintJobStatus status) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bool done = true;
api::printing::JobStatus job_status;
switch (status) {
case crosapi::mojom::PrintJobStatus::kStarted:
job_status = api::printing::JOB_STATUS_IN_PROGRESS;
done = false;
break;
case crosapi::mojom::PrintJobStatus::kDone:
job_status = api::printing::JOB_STATUS_PRINTED;
break;
case crosapi::mojom::PrintJobStatus::kError:
job_status = api::printing::JOB_STATUS_FAILED;
break;
case crosapi::mojom::PrintJobStatus::kCancelled:
job_status = api::printing::JOB_STATUS_CANCELED;
break;
default: // crosapi::mojom::PrintJobStatus::kCreated
return;
}
std::string cups_id = CreateUniqueId(printer_id, job_id);
auto it = print_jobs_.find(cups_id);
if (it == print_jobs_.end())
return;
const std::string& extension_id = it->second.extension_id;
if (extension_registry_->enabled_extensions().Contains(extension_id)) {
auto event = std::make_unique<Event>(
events::PRINTING_ON_JOB_STATUS_CHANGED,
api::printing::OnJobStatusChanged::kEventName,
api::printing::OnJobStatusChanged::Create(cups_id, job_status));
event_router_->DispatchEventToExtension(extension_id, std::move(event));
}
if (done)
print_jobs_.erase(it);
}
template <>
KeyedService*
BrowserContextKeyedAPIFactory<PrintingAPIHandler>::BuildServiceInstanceFor(
content::BrowserContext* context) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Profile* profile = Profile::FromBrowserContext(context);
// We do not want an instance of PrintingAPIHandler on the lock screen.
// This will lead to multiple printing notifications.
if (!profile->IsRegularProfile()) {
return nullptr;
}
return new PrintingAPIHandler(context);
}
} // namespace extensions