blob: 1db9144528eacb873728306be81573628c6f770e [file] [log] [blame]
// Copyright 2015 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/ui/webui/print_preview/extension_printer_handler.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/printing/pwg_raster_converter.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_utils.h"
#include "components/cloud_devices/common/cloud_device_description.h"
#include "components/cloud_devices/common/printer_description.h"
#include "extensions/browser/api/device_permissions_manager.h"
#include "extensions/browser/api/printer_provider/printer_provider_api.h"
#include "extensions/browser/api/printer_provider/printer_provider_api_factory.h"
#include "extensions/browser/api/printer_provider/printer_provider_print_job.h"
#include "extensions/browser/api/usb/usb_device_manager.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/usb_device_permission.h"
#include "extensions/common/permissions/usb_device_permission_data.h"
#include "extensions/common/value_builder.h"
#include "printing/print_job_constants.h"
#include "printing/pwg_raster_settings.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
using extensions::DevicePermissionsManager;
using extensions::DictionaryBuilder;
using extensions::Extension;
using extensions::ExtensionRegistry;
using extensions::ListBuilder;
using extensions::UsbDeviceManager;
using extensions::UsbPrinterManifestData;
namespace printing {
namespace {
const char kContentTypePdf[] = "application/pdf";
const char kContentTypePWGRaster[] = "image/pwg-raster";
const char kContentTypeAll[] = "*/*";
const char kInvalidDataPrintError[] = "INVALID_DATA";
const char kInvalidTicketPrintError[] = "INVALID_TICKET";
const char kProvisionalUsbLabel[] = "provisional-usb";
// Updates |job| with raster data. Returns the updated print job.
void UpdateJobFileInfo(std::unique_ptr<extensions::PrinterProviderPrintJob> job,
ExtensionPrinterHandler::PrintJobCallback callback,
base::ReadOnlySharedMemoryRegion pwg_region) {
auto data =
base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(pwg_region);
if (data)
job->document_bytes = data;
std::move(callback).Run(std::move(job));
}
bool HasUsbPrinterProviderPermissions(const Extension* extension) {
return extension->permissions_data() &&
extension->permissions_data()->HasAPIPermission(
extensions::mojom::APIPermissionID::kPrinterProvider) &&
extension->permissions_data()->HasAPIPermission(
extensions::mojom::APIPermissionID::kUsb);
}
std::string GenerateProvisionalUsbPrinterId(
const Extension* extension,
const device::mojom::UsbDeviceInfo& device) {
return base::StringPrintf("%s:%s:%s", kProvisionalUsbLabel,
extension->id().c_str(), device.guid.c_str());
}
struct ProvisionalUsbPrinter {
std::string extension_id;
std::string device_guid;
};
absl::optional<ProvisionalUsbPrinter> ParseProvisionalUsbPrinterId(
const std::string& printer_id) {
std::vector<std::string> components = base::SplitString(
printer_id, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (components.size() != 3 || components[0] != kProvisionalUsbLabel)
return absl::nullopt;
return ProvisionalUsbPrinter{.extension_id = std::move(components[1]),
.device_guid = std::move(components[2])};
}
extensions::PrinterProviderAPI* GetPrinterProviderAPI(Profile* profile) {
return extensions::PrinterProviderAPIFactory::GetInstance()
->GetForBrowserContext(profile);
}
} // namespace
ExtensionPrinterHandler::ExtensionPrinterHandler(Profile* profile)
: profile_(profile) {}
ExtensionPrinterHandler::~ExtensionPrinterHandler() {
}
void ExtensionPrinterHandler::Reset() {
// TODO(tbarzic): Keep track of pending request ids issued by |this| and
// cancel them from here.
pending_enumeration_count_ = 0;
weak_ptr_factory_.InvalidateWeakPtrs();
}
void ExtensionPrinterHandler::StartGetPrinters(
AddedPrintersCallback callback,
GetPrintersDoneCallback done_callback) {
// Assume that there can only be one printer enumeration occuring at once.
DCHECK_EQ(pending_enumeration_count_, 0);
pending_enumeration_count_ = 1;
done_callback_ = std::move(done_callback);
bool extension_supports_usb_printers = false;
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
for (const auto& extension : registry->enabled_extensions()) {
if (UsbPrinterManifestData::Get(extension.get()) &&
HasUsbPrinterProviderPermissions(extension.get())) {
extension_supports_usb_printers = true;
break;
}
}
if (extension_supports_usb_printers) {
pending_enumeration_count_++;
UsbDeviceManager* usb_manager = UsbDeviceManager::Get(profile_);
DCHECK(usb_manager);
usb_manager->GetDevices(
base::BindOnce(&ExtensionPrinterHandler::OnUsbDevicesEnumerated,
weak_ptr_factory_.GetWeakPtr(), callback));
}
GetPrinterProviderAPI(profile_)->DispatchGetPrintersRequested(
base::BindRepeating(&ExtensionPrinterHandler::WrapGetPrintersCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExtensionPrinterHandler::StartGetCapability(
const std::string& destination_id,
GetCapabilityCallback callback) {
GetPrinterProviderAPI(profile_)->DispatchGetCapabilityRequested(
destination_id,
base::BindOnce(&ExtensionPrinterHandler::WrapGetCapabilityCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExtensionPrinterHandler::StartPrint(
const std::u16string& job_title,
base::Value::Dict settings,
scoped_refptr<base::RefCountedMemory> print_data,
PrintCallback callback) {
auto print_job = std::make_unique<extensions::PrinterProviderPrintJob>();
print_job->job_title = job_title;
std::string capabilities;
gfx::Size page_size;
if (!ParseSettings(settings, &print_job->printer_id, &capabilities,
&page_size, &print_job->ticket)) {
std::move(callback).Run(base::Value("Invalid settings"));
return;
}
cloud_devices::CloudDeviceDescription printer_description;
printer_description.InitFromString(capabilities);
cloud_devices::printer::ContentTypesCapability content_types;
content_types.LoadFrom(printer_description);
const bool use_pdf = content_types.Contains(kContentTypePdf) ||
content_types.Contains(kContentTypeAll);
if (use_pdf) {
print_job->content_type = kContentTypePdf;
print_job->document_bytes = print_data;
DispatchPrintJob(std::move(callback), std::move(print_job));
return;
}
cloud_devices::CloudDeviceDescription ticket;
if (!ticket.InitFromValue(print_job->ticket.Clone())) {
WrapPrintCallback(std::move(callback),
base::Value(kInvalidTicketPrintError));
return;
}
print_job->content_type = kContentTypePWGRaster;
ConvertToPWGRaster(
print_data, printer_description, ticket, page_size, std::move(print_job),
base::BindOnce(&ExtensionPrinterHandler::DispatchPrintJob,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExtensionPrinterHandler::StartGrantPrinterAccess(
const std::string& printer_id,
GetPrinterInfoCallback callback) {
absl::optional<ProvisionalUsbPrinter> printer =
ParseProvisionalUsbPrinterId(printer_id);
if (!printer.has_value()) {
std::move(callback).Run(base::Value::Dict());
return;
}
const device::mojom::UsbDeviceInfo* device =
UsbDeviceManager::Get(profile_)->GetDeviceInfo(
printer.value().device_guid);
if (!device) {
std::move(callback).Run(base::Value::Dict());
return;
}
DevicePermissionsManager* permissions_manager =
DevicePermissionsManager::Get(profile_);
permissions_manager->AllowUsbDevice(printer.value().extension_id, *device);
GetPrinterProviderAPI(profile_)->DispatchGetUsbPrinterInfoRequested(
printer.value().extension_id, *device,
base::BindOnce(&ExtensionPrinterHandler::WrapGetPrinterInfoCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExtensionPrinterHandler::SetPwgRasterConverterForTesting(
std::unique_ptr<PwgRasterConverter> pwg_raster_converter) {
pwg_raster_converter_ = std::move(pwg_raster_converter);
}
void ExtensionPrinterHandler::ConvertToPWGRaster(
scoped_refptr<base::RefCountedMemory> data,
const cloud_devices::CloudDeviceDescription& printer_description,
const cloud_devices::CloudDeviceDescription& print_ticket,
const gfx::Size& page_size,
std::unique_ptr<extensions::PrinterProviderPrintJob> job,
PrintJobCallback callback) {
if (!pwg_raster_converter_)
pwg_raster_converter_ = PwgRasterConverter::CreateDefault();
PwgRasterSettings bitmap_settings =
PwgRasterConverter::GetBitmapSettings(printer_description, print_ticket);
pwg_raster_converter_->Start(
data.get(),
PwgRasterConverter::GetConversionSettings(printer_description, page_size,
bitmap_settings.use_color),
bitmap_settings,
base::BindOnce(&UpdateJobFileInfo, std::move(job), std::move(callback)));
}
void ExtensionPrinterHandler::DispatchPrintJob(
PrintCallback callback,
std::unique_ptr<extensions::PrinterProviderPrintJob> print_job) {
if (!print_job->document_bytes) {
WrapPrintCallback(std::move(callback), base::Value(kInvalidDataPrintError));
return;
}
extensions::PrinterProviderAPIFactory::GetInstance()
->GetForBrowserContext(profile_)
->DispatchPrintRequested(
std::move(*print_job),
base::BindOnce(&ExtensionPrinterHandler::WrapPrintCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExtensionPrinterHandler::WrapGetPrintersCallback(
AddedPrintersCallback callback,
base::Value::List printers,
bool done) {
DCHECK_GT(pending_enumeration_count_, 0);
if (!printers.empty())
callback.Run(std::move(printers));
if (done)
pending_enumeration_count_--;
if (pending_enumeration_count_ == 0)
std::move(done_callback_).Run();
}
void ExtensionPrinterHandler::WrapGetCapabilityCallback(
GetCapabilityCallback callback,
base::Value::Dict capability) {
base::Value::Dict capabilities;
base::Value::Dict cdd = ValidateCddForPrintPreview(std::move(capability));
// Leave |capabilities| empty if |cdd| is empty.
if (!cdd.empty()) {
capabilities.Set(kSettingCapabilities,
UpdateCddWithDpiIfMissing(std::move(cdd)));
}
std::move(callback).Run(std::move(capabilities));
}
void ExtensionPrinterHandler::WrapPrintCallback(PrintCallback callback,
const base::Value& status) {
std::move(callback).Run(status);
}
void ExtensionPrinterHandler::WrapGetPrinterInfoCallback(
GetPrinterInfoCallback callback,
base::Value::Dict printer_info) {
std::move(callback).Run(std::move(printer_info));
}
void ExtensionPrinterHandler::OnUsbDevicesEnumerated(
AddedPrintersCallback callback,
std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
DevicePermissionsManager* permissions_manager =
DevicePermissionsManager::Get(profile_);
ListBuilder printer_list;
for (const auto& extension : registry->enabled_extensions()) {
const UsbPrinterManifestData* manifest_data =
UsbPrinterManifestData::Get(extension.get());
if (!manifest_data || !HasUsbPrinterProviderPermissions(extension.get()))
continue;
const extensions::DevicePermissions* device_permissions =
permissions_manager->GetForExtension(extension->id());
for (const auto& device : devices) {
if (manifest_data->SupportsDevice(*device)) {
std::unique_ptr<extensions::UsbDevicePermission::CheckParam> param =
extensions::UsbDevicePermission::CheckParam::ForUsbDevice(
extension.get(), *device);
if (device_permissions->FindUsbDeviceEntry(*device) ||
extension->permissions_data()->CheckAPIPermissionWithParam(
extensions::mojom::APIPermissionID::kUsbDevice, param.get())) {
// Skip devices the extension already has permission to access.
continue;
}
printer_list.Append(
DictionaryBuilder()
.Set("id",
GenerateProvisionalUsbPrinterId(extension.get(), *device))
.Set("name",
DevicePermissionsManager::GetPermissionMessage(
device->vendor_id, device->product_id,
device->manufacturer_name.value_or(std::u16string()),
device->product_name.value_or(std::u16string()),
std::u16string(), false))
.Set("extensionId", extension->id())
.Set("extensionName", extension->name())
.Set("provisional", true)
.Build());
}
}
}
DCHECK_GT(pending_enumeration_count_, 0);
pending_enumeration_count_--;
base::Value::List list = printer_list.Build();
if (!list.empty())
callback.Run(std::move(list));
if (pending_enumeration_count_ == 0)
std::move(done_callback_).Run();
}
} // namespace printing