// Copyright 2023 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_test_utils.h"
#include <string_view>
#include "base/check_deref.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/map_util.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/printing/local_printer_utils_chromeos.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chromeos/printing/printer_configuration.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/common/constants.h"
#include "extensions/test/test_extension_dir.h"
#include "printing/backend/cups_ipp_constants.h"
#include "printing/backend/print_backend.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/printing/cups_print_job_manager.h"
#include "chrome/browser/ash/printing/cups_print_job_manager_factory.h"
#include "chrome/browser/ash/printing/cups_printers_manager_factory.h"
#include "chrome/browser/ash/printing/fake_cups_print_job_manager.h"
#include "chrome/browser/ash/printing/fake_cups_printers_manager.h"
#include "chrome/browser/ash/printing/history/print_job_info.pb.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/browser/printing/print_job_manager.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "printing/backend/test_print_backend.h"
#include "printing/printing_context.h"
#endif
#if BUILDFLAG(ENABLE_OOP_PRINTING)
#include "base/feature_list.h"
#include "chrome/browser/printing/print_backend_service_manager.h"
#include "chrome/browser/printing/print_backend_service_test_impl.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "printing/printing_features.h"
#endif
namespace extensions {
namespace {
constexpr int kHorizontalDpi = 300;
constexpr int kVerticalDpi = 400;
// Size of ISO A4 paper in microns.
constexpr int kIsoA4Width = 210000;
constexpr int kIsoA4Height = 297000;
// Size of NA Letter paper in microns.
constexpr int kNaLetterWidth = 215900;
constexpr int kNaLetterHeight = 279400;
// Size of a custom paper with variable height in microns.
constexpr int kCustomPaperWidth = 200000;
constexpr int kCustomPaperHeight = 250000;
constexpr int kCustomPaperMaxHeight = 300000;
// Mapping of the different extension types used in the test to the specific
// manifest file names to create an extension of that type. The actual location
// of these files is at //chrome/test/data/extensions/api_test/printing/.
static constexpr auto kManifestFileNames =
base::MakeFixedFlatMap<ExtensionType, std::string_view>(
{{ExtensionType::kChromeApp, "manifest_chrome_app.json"},
{ExtensionType::kExtensionMV2, "manifest_extension.json"},
{ExtensionType::kExtensionMV3, "manifest_v3_extension.json"}});
#if BUILDFLAG(IS_CHROMEOS_ASH)
// This class uses methods from FakeCupsPrintJobManager while connecting it to
// the rest of the printing pipeline so that it no longer has to be directly
// invoked by the test code.
class FakePrintJobManagerWithDocDone : public ash::FakeCupsPrintJobManager {
public:
explicit FakePrintJobManagerWithDocDone(Profile* profile)
: FakeCupsPrintJobManager(profile) {
subscription_ = g_browser_process->print_job_manager()->AddDocDoneCallback(
base::BindRepeating(&FakePrintJobManagerWithDocDone::OnDocDone,
base::Unretained(this)));
}
void OnDocDone(printing::PrintJob* job,
printing::PrintedDocument* document,
int job_id) {
const auto& settings = document->settings();
// ash::printing::proto::PrintSettings are only useful for real pipelines
// for logging print data; this can be omitted in tests.
CreatePrintJob(
base::UTF16ToUTF8(settings.device_name()),
base::UTF16ToUTF8(settings.title()), job_id,
/*total_page_number=*/document->page_count() * settings.copies(),
job->source(), job->source_id(), ash::printing::proto::PrintSettings());
}
private:
printing::PrintJobManager::DocDoneCallbackList::Subscription subscription_;
};
std::unique_ptr<KeyedService> BuildFakeCupsPrintJobManagerWithDocDone(
content::BrowserContext* context) {
return std::make_unique<FakePrintJobManagerWithDocDone>(
Profile::FromBrowserContext(context));
}
std::unique_ptr<KeyedService> BuildFakeCupsPrintersManager(
content::BrowserContext* context) {
return std::make_unique<ash::FakeCupsPrintersManager>();
}
#endif
} // namespace
PrintingBackendInfrastructureHelper::PrintingBackendInfrastructureHelper()
: test_print_backend_(base::MakeRefCounted<printing::TestPrintBackend>()) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
printing::PrintBackend::SetPrintBackendForTesting(test_print_backend_.get());
printing::PrintingContext::SetPrintingContextFactoryForTest(
&test_printing_context_factory_);
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (base::FeatureList::IsEnabled(
printing::features::kEnableOopPrintDrivers)) {
print_backend_service_ =
printing::PrintBackendServiceTestImpl::LaunchForTesting(
test_remote_, test_print_backend_.get(), /*sandboxed=*/true);
// Replace disconnect handler.
test_remote_.reset_on_disconnect();
}
#endif
}
PrintingBackendInfrastructureHelper::~PrintingBackendInfrastructureHelper() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
printing::PrintingContext::SetPrintingContextFactoryForTest(nullptr);
printing::PrintBackend::SetPrintBackendForTesting(nullptr);
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (base::FeatureList::IsEnabled(
printing::features::kEnableOopPrintDrivers)) {
printing::PrintBackendServiceManager::GetInstance().ResetForTesting();
}
#endif
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
PrintingTestHelper::PrintingTestHelper() {
create_services_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
&PrintingTestHelper::OnWillCreateBrowserContextServices,
base::Unretained(this)));
}
PrintingTestHelper::~PrintingTestHelper() = default;
void PrintingTestHelper::Init(Profile* profile) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
profile_ = profile;
printing_infra_helper_ =
std::make_unique<PrintingBackendInfrastructureHelper>();
}
void PrintingTestHelper::AddAvailablePrinter(
const std::string& printer_id,
const std::string& printer_display_name,
std::unique_ptr<printing::PrinterSemanticCapsAndDefaults> capabilities) {
CHECK(profile_);
CHECK(printing_infra_helper_);
chromeos::Printer printer;
printer.set_id(printer_id);
printer.set_display_name(printer_display_name);
printer.SetUri("ipp://192.168.1.0");
auto* printers_manager = static_cast<ash::FakeCupsPrintersManager*>(
ash::CupsPrintersManagerFactory::GetForBrowserContext(profile_));
printers_manager->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
chromeos::CupsPrinterStatus status(printer_id);
status.AddStatusReason(
chromeos::CupsPrinterStatus::CupsPrinterStatusReason::Reason::
kPrinterUnreachable,
chromeos::CupsPrinterStatus::CupsPrinterStatusReason::Severity::kError);
printers_manager->SetPrinterStatus(status);
printing_infra_helper_->test_print_backend().AddValidPrinter(
printer_id, std::move(capabilities), nullptr);
// Printers in the test context are identified by `printer_id`.
printing_infra_helper_->test_printing_context_factory()
.SetPrinterNameForSubsequentContexts(printer_id);
}
void PrintingTestHelper::OnWillCreateBrowserContextServices(
content::BrowserContext* context) {
ash::CupsPrintJobManagerFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating(&BuildFakeCupsPrintJobManagerWithDocDone));
ash::CupsPrintersManagerFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating(&BuildFakeCupsPrintersManager));
}
#endif
std::unique_ptr<TestExtensionDir> CreatePrintingExtension(ExtensionType type) {
auto extension_dir = std::make_unique<TestExtensionDir>();
base::FilePath test_data_dir;
CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
base::FilePath printing_dir = test_data_dir.AppendASCII("extensions")
.AppendASCII("api_test")
.AppendASCII("printing");
base::ScopedAllowBlockingForTesting allow_blocking;
base::CopyDirectory(printing_dir, extension_dir->UnpackedPath(),
/*recursive=*/false);
extension_dir->CopyFileTo(printing_dir.AppendASCII(CHECK_DEREF(
base::FindOrNull(kManifestFileNames, type))),
extensions::kManifestFilename);
return extension_dir;
}
std::unique_ptr<printing::PrinterSemanticCapsAndDefaults>
ConstructPrinterCapabilities() {
auto capabilities =
std::make_unique<printing::PrinterSemanticCapsAndDefaults>();
capabilities->bw_model = printing::mojom::ColorModel::kGray;
capabilities->color_model = printing::mojom::ColorModel::kColor;
capabilities->duplex_default = printing::mojom::DuplexMode::kSimplex;
capabilities->duplex_modes.push_back(printing::mojom::DuplexMode::kSimplex);
capabilities->copies_max = 2;
capabilities->default_dpi = {kHorizontalDpi, kVerticalDpi};
capabilities->dpis.emplace_back(capabilities->default_dpi);
printing::PrinterSemanticCapsAndDefaults::Paper iso_a4_paper(
/*display_name=*/"", /*vendor_id=*/"", {kIsoA4Width, kIsoA4Height});
printing::PrinterSemanticCapsAndDefaults::Paper na_letter_paper(
/*display_name=*/"", /*vendor_id=*/"", {kNaLetterWidth, kNaLetterHeight});
printing::PrinterSemanticCapsAndDefaults::Paper custom_paper(
/*display_name=*/"", /*vendor_id=*/"",
{kCustomPaperWidth, kCustomPaperHeight},
/*printable_area_um=*/{kCustomPaperWidth, kCustomPaperHeight},
/*max_height_um=*/kCustomPaperMaxHeight);
capabilities->default_paper = iso_a4_paper;
capabilities->papers = {std::move(iso_a4_paper), std::move(na_letter_paper),
std::move(custom_paper)};
capabilities->collate_capable = true;
std::vector<printing::AdvancedCapabilityValue> media_source_vals(
{{"auto", ""}, {"tray-1", ""}});
capabilities->advanced_capabilities.emplace_back(
/*name=*/printing::kIppMediaSource, /*localized_name=*/"",
printing::AdvancedCapability::Type::kString, /*default_value=*/"auto",
/*values=*/std::move(media_source_vals));
return capabilities;
}
std::vector<crosapi::mojom::LocalDestinationInfoPtr>
ConstructGetPrintersResponse(const std::string& printer_id,
const std::string& printer_name) {
chromeos::Printer printer;
printer.set_id(printer_id);
printer.set_display_name(printer_name);
std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers;
printers.push_back(printing::PrinterToMojom(printer));
return printers;
}
} // namespace extensions