chromium/chrome/services/printing/print_backend_service_impl.cc

// Copyright 2020 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/services/printing/print_backend_service_impl.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/adapters.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/not_fatal_until.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequence_bound.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/common/printing/printing_init.h"
#include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
#include "components/crash/core/common/crash_keys.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "printing/backend/print_backend.h"
#include "printing/buildflags/buildflags.h"
#include "printing/metafile.h"
#include "printing/metafile_skia.h"
#include "printing/mojom/print.mojom.h"
#include "printing/printed_document.h"
#include "printing/printing_context.h"

#if BUILDFLAG(IS_MAC)
#include "base/threading/thread_restrictions.h"
#include "chrome/common/printing/printer_capabilities_mac.h"
#endif

#if BUILDFLAG(IS_CHROMEOS) && BUILDFLAG(USE_CUPS)
#include "printing/backend/cups_connection_pool.h"
#endif

#if BUILDFLAG(IS_LINUX)
#include "base/no_destructor.h"
#include "ui/linux/linux_ui.h"
#include "ui/linux/linux_ui_delegate_stub.h"
#include "ui/linux/linux_ui_factory.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "base/containers/queue.h"
#include "base/types/expected.h"
#include "base/win/win_util.h"
#include "chrome/services/printing/public/mojom/printer_xml_parser.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "printing/backend/xps_utils_win.h"
#include "printing/emf_win.h"
#include "printing/printed_page_win.h"
#include "printing/printing_features.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/native_widget_types.h"
#endif

namespace printing {

namespace {

#if BUILDFLAG(IS_LINUX)
void InstantiateLinuxUiDelegate() {}
#endif

scoped_refptr<base::SequencedTaskRunner> GetPrintingTaskRunner() {}

std::unique_ptr<Metafile> CreateMetafile(mojom::MetafileDataType data_type) {}

struct RenderData {};

std::optional<RenderData> PrepareRenderData(
    int document_cookie,
    mojom::MetafileDataType page_data_type,
    const base::ReadOnlySharedMemoryRegion& serialized_data) {}

// Local storage of document and associated data needed to submit to job to
// the operating system's printing API.  All access to the document occurs on
// a worker task runner.
class DocumentContainer {};

PrintBackendServiceImpl::StartPrintingResult
DocumentContainer::StartPrintingReadyDocument() {}

#if BUILDFLAG(IS_WIN)
mojom::ResultCode DocumentContainer::DoRenderPrintedPage(
    uint32_t page_index,
    mojom::MetafileDataType page_data_type,
    base::ReadOnlySharedMemoryRegion serialized_page,
    gfx::Size page_size,
    gfx::Rect page_content_rect,
    float shrink_factor) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DVLOG(1) << "Render printed page " << page_index << " for document "
           << document_->cookie();

  std::optional<RenderData> render_data =
      PrepareRenderData(document_->cookie(), page_data_type, serialized_page);
  if (!render_data) {
    DLOG(ERROR) << "Failure preparing render data for document "
                << document_->cookie();
    context_->Cancel();
    return mojom::ResultCode::kFailed;
  }

  document_->SetPage(page_index, std::move(render_data->metafile),
                     shrink_factor, page_size, page_content_rect);

  mojom::ResultCode result = document_->RenderPrintedPage(
      *document_->GetPage(page_index), context_.get());
  if (result != mojom::ResultCode::kSuccess) {
    DLOG(ERROR) << "Failure rendering page " << page_index << " of document "
                << document_->cookie() << ", error: " << result;
    context_->Cancel();
  }
  return result;
}
#endif  // BUILDFLAG(IS_WIN)

mojom::ResultCode DocumentContainer::DoRenderPrintedDocument(
    uint32_t page_count,
    mojom::MetafileDataType data_type,
    base::ReadOnlySharedMemoryRegion serialized_document) {}

mojom::ResultCode DocumentContainer::DoDocumentDone() {}

void DocumentContainer::DoCancel() {}

}  // namespace

// Helper for managing `DocumentContainer` objects.  All access to this occurs
// on the main thread.
class PrintBackendServiceImpl::DocumentHelper {};

// Sandboxed service helper.
SandboxedPrintBackendHostImpl::SandboxedPrintBackendHostImpl(
    mojo::PendingReceiver<mojom::SandboxedPrintBackendHost> receiver)
    :{}

SandboxedPrintBackendHostImpl::~SandboxedPrintBackendHostImpl() = default;

void SandboxedPrintBackendHostImpl::BindBackend(
    mojo::PendingReceiver<mojom::PrintBackendService> receiver) {}

// Unsandboxed service helper.
UnsandboxedPrintBackendHostImpl::UnsandboxedPrintBackendHostImpl(
    mojo::PendingReceiver<mojom::UnsandboxedPrintBackendHost> receiver)
    :{}

UnsandboxedPrintBackendHostImpl::~UnsandboxedPrintBackendHostImpl() = default;

void UnsandboxedPrintBackendHostImpl::BindBackend(
    mojo::PendingReceiver<mojom::PrintBackendService> receiver) {}

PrintBackendServiceImpl::PrintingContextDelegate::PrintingContextDelegate() =
    default;
PrintBackendServiceImpl::PrintingContextDelegate::~PrintingContextDelegate() =
    default;

gfx::NativeView
PrintBackendServiceImpl::PrintingContextDelegate::GetParentView() {}

std::string PrintBackendServiceImpl::PrintingContextDelegate::GetAppLocale() {}

#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
void PrintBackendServiceImpl::PrintingContextDelegate::SetParentWindow(
    uint32_t parent_window_id) {
#if BUILDFLAG(IS_WIN)
  parent_native_view_ = reinterpret_cast<gfx::NativeView>(
      base::win::Uint32ToHandle(parent_window_id));
#else
  NOTREACHED_IN_MIGRATION();
#endif
}
#endif

void PrintBackendServiceImpl::PrintingContextDelegate::SetAppLocale(
    const std::string& locale) {}

// Holds the context and associated delegate for persistent usage across
// multiple settings calls until they are ready to be used to print a
// document.  Required since `PrintingContext` does not own the corresponding
// delegate object that it relies upon.
struct PrintBackendServiceImpl::ContextContainer {};

PrintBackendServiceImpl::PrintBackendServiceImpl(
    mojo::PendingReceiver<mojom::PrintBackendService> receiver)
    :{}

PrintBackendServiceImpl::~PrintBackendServiceImpl() = default;

void PrintBackendServiceImpl::InitCommon(
#if BUILDFLAG(IS_WIN)
    const std::string& locale,
    mojo::PendingRemote<mojom::PrinterXmlParser> remote
#else
    const std::string& locale
#endif  // BUILDFLAG(IS_WIN)
) {}

void PrintBackendServiceImpl::Init(
#if BUILDFLAG(IS_WIN)
    const std::string& locale,
    mojo::PendingRemote<mojom::PrinterXmlParser> remote
#else
    const std::string& locale
#endif  // BUILDFLAG(IS_WIN)
) {}

// TODO(crbug.com/40775634)  Do nothing, this is just to assist an idle timeout
// change by providing a low-cost call to ensure it is applied.
void PrintBackendServiceImpl::Poke() {}

void PrintBackendServiceImpl::EnumeratePrinters(
    mojom::PrintBackendService::EnumeratePrintersCallback callback) {}

void PrintBackendServiceImpl::GetDefaultPrinterName(
    mojom::PrintBackendService::GetDefaultPrinterNameCallback callback) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
void PrintBackendServiceImpl::GetPrinterSemanticCapsAndDefaults(
    const std::string& printer_name,
    mojom::PrintBackendService::GetPrinterSemanticCapsAndDefaultsCallback
        callback) {
  DCHECK(print_backend_);
  crash_keys_ = std::make_unique<crash_keys::ScopedPrinterInfo>(
      printer_name, print_backend_->GetPrinterDriverInfo(printer_name));

  PrinterSemanticCapsAndDefaults printer_caps;
  const mojom::ResultCode result =
      print_backend_->GetPrinterSemanticCapsAndDefaults(printer_name,
                                                        &printer_caps);
  if (result != mojom::ResultCode::kSuccess) {
    std::move(callback).Run(
        mojom::PrinterSemanticCapsAndDefaultsResult::NewResultCode(result));
    return;
  }
  std::move(callback).Run(
      mojom::PrinterSemanticCapsAndDefaultsResult::NewPrinterCaps(
          std::move(printer_caps)));
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

void PrintBackendServiceImpl::FetchCapabilities(
    const std::string& printer_name,
    mojom::PrintBackendService::FetchCapabilitiesCallback callback) {}

#if BUILDFLAG(IS_WIN)
void PrintBackendServiceImpl::GetPaperPrintableArea(
    const std::string& printer_name,
    const PrintSettings::RequestedMedia& media,
    mojom::PrintBackendService::GetPaperPrintableAreaCallback callback) {
  CHECK(print_backend_);
  crash_keys_ = std::make_unique<crash_keys::ScopedPrinterInfo>(
      printer_name, print_backend_->GetPrinterDriverInfo(printer_name));

  std::optional<gfx::Rect> printable_area_um =
      print_backend_->GetPaperPrintableArea(printer_name, media.vendor_id,
                                            media.size_microns);
  std::move(callback).Run(printable_area_um.value_or(gfx::Rect()));
}
#endif

void PrintBackendServiceImpl::EstablishPrintingContext(uint32_t context_id
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
                                                       ,
                                                       uint32_t parent_window_id
#endif
) {}

void PrintBackendServiceImpl::UseDefaultSettings(
    uint32_t context_id,
    mojom::PrintBackendService::UseDefaultSettingsCallback callback) {}

#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
void PrintBackendServiceImpl::AskUserForSettings(
    uint32_t context_id,
    int max_pages,
    bool has_selection,
    bool is_scripted,
    mojom::PrintBackendService::AskUserForSettingsCallback callback) {
  // Safe to use `base::Unretained(this)` because `this` outlives the async
  // call and callback.  The entire service process goes away when `this`
  // lifetime expires.
  PrintingContext* context = GetPrintingContext(context_id);
  CHECK(context) << "No context found for id " << context_id;
  context->AskUserForSettings(
      max_pages, has_selection, is_scripted,
      base::BindOnce(&PrintBackendServiceImpl::OnDidAskUserForSettings,
                     base::Unretained(this), context_id, std::move(callback)));
}
#endif  // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)

void PrintBackendServiceImpl::UpdatePrintSettings(
    uint32_t context_id,
    base::Value::Dict job_settings,
    mojom::PrintBackendService::UpdatePrintSettingsCallback callback) {}

void PrintBackendServiceImpl::StartPrinting(
    uint32_t context_id,
    int document_cookie,
    const std::u16string& document_name,
#if !BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
    const std::optional<PrintSettings>& settings,
#endif
    mojom::PrintBackendService::StartPrintingCallback callback) {}

#if BUILDFLAG(IS_WIN)
void PrintBackendServiceImpl::RenderPrintedPage(
    int32_t document_cookie,
    uint32_t page_index,
    mojom::MetafileDataType page_data_type,
    base::ReadOnlySharedMemoryRegion serialized_page,
    const gfx::Size& page_size,
    const gfx::Rect& page_content_rect,
    float shrink_factor,
    mojom::PrintBackendService::RenderPrintedPageCallback callback) {
  DocumentHelper* document_helper = GetDocumentHelper(document_cookie);
  DCHECK(document_helper);

  document_helper->document_container()
      .AsyncCall(&DocumentContainer::DoRenderPrintedPage)
      .WithArgs(page_index, page_data_type, std::move(serialized_page),
                page_size, page_content_rect, shrink_factor)
      .Then(std::move(callback));
}
#endif  // BUILDFLAG(IS_WIN)

void PrintBackendServiceImpl::RenderPrintedDocument(
    int32_t document_cookie,
    uint32_t page_count,
    mojom::MetafileDataType data_type,
    base::ReadOnlySharedMemoryRegion serialized_document,
    mojom::PrintBackendService::RenderPrintedDocumentCallback callback) {}

void PrintBackendServiceImpl::DocumentDone(
    int document_cookie,
    mojom::PrintBackendService::DocumentDoneCallback callback) {}

void PrintBackendServiceImpl::Cancel(
    int document_cookie,
    mojom::PrintBackendService::CancelCallback callback) {}

#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
void PrintBackendServiceImpl::OnDidAskUserForSettings(
    uint32_t context_id,
    mojom::PrintBackendService::AskUserForSettingsCallback callback,
    mojom::ResultCode result) {
  auto* context = GetPrintingContext(context_id);
  DCHECK(context);
  if (result != mojom::ResultCode::kSuccess) {
    DLOG(ERROR) << "Did not get user settings, error: " << result;
    persistent_printing_contexts_.erase(context_id);
    std::move(callback).Run(mojom::PrintSettingsResult::NewResultCode(result));
    return;
  }

  // Set the crash keys for the document that should start printing next.
  std::string printer_name =
      base::UTF16ToUTF8(context->settings().device_name());
  crash_keys_ = std::make_unique<crash_keys::ScopedPrinterInfo>(
      printer_name, print_backend_->GetPrinterDriverInfo(printer_name));

  std::move(callback).Run(mojom::PrintSettingsResult::NewSettings(
      *context->TakeAndResetSettings()));
}
#endif  // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)

void PrintBackendServiceImpl::OnDidStartPrintingReadyDocument(
    DocumentHelper& document_helper,
    StartPrintingResult printing_result) {}

void PrintBackendServiceImpl::OnDidDocumentDone(
    DocumentHelper& document_helper,
    mojom::PrintBackendService::DocumentDoneCallback callback,
    mojom::ResultCode result) {}

void PrintBackendServiceImpl::OnDidCancel(
    DocumentHelper& document_helper,
    mojom::PrintBackendService::CancelCallback callback) {}

std::unique_ptr<PrintBackendServiceImpl::PrintingContextDelegate>
PrintBackendServiceImpl::CreatePrintingContextDelegate() {}

PrintingContext* PrintBackendServiceImpl::GetPrintingContext(
    uint32_t context_id) {}

PrintBackendServiceImpl::DocumentHelper*
PrintBackendServiceImpl::GetDocumentHelper(int document_cookie) {}

void PrintBackendServiceImpl::RemoveDocumentHelper(
    DocumentHelper& document_helper) {}

#if BUILDFLAG(IS_WIN)
base::expected<XpsCapabilities, mojom::ResultCode>
PrintBackendServiceImpl::GetXpsCapabilities(const std::string& printer_name) {
  ASSIGN_OR_RETURN(
      std::string xml,
      print_backend_->GetXmlPrinterCapabilitiesForXpsDriver(printer_name),
      [&](mojom::ResultCode error) {
        DLOG(ERROR) << "Failure getting XPS capabilities of printer "
                    << printer_name << ", error: " << error;
        return error;
      });

  mojom::PrinterCapabilitiesValueResultPtr value_result;
  if (!xml_parser_remote_->ParseXmlForPrinterCapabilities(xml, &value_result)) {
    DLOG(ERROR) << "Failure parsing XML of XPS capabilities of printer "
                << printer_name
                << ", error: ParseXmlForPrinterCapabilities failed";
    return base::unexpected(mojom::ResultCode::kFailed);
  }
  if (value_result->is_result_code()) {
    DLOG(ERROR) << "Failure parsing XML of XPS capabilities of printer "
                << printer_name
                << ", error: " << value_result->get_result_code();
    return base::unexpected(value_result->get_result_code());
  }

  return ParseValueForXpsPrinterCapabilities(value_result->get_capabilities())
      .transform_error([&](mojom::ResultCode code) {
        DLOG(ERROR) << "Failure parsing value of XPS capabilities of printer "
                    << printer_name << ", error: " << code;
        return code;
      });
}
#endif  // BUILDFLAG(IS_WIN)

}  // namespace printing