0

Use faster method to EnumeratePrinters on Windows

On Windows, the set of Print Spooler API calls used to get the list of
installed printers can require opening a connection to each printer.
This might potentially block waiting on network connections to remote
printers.  Such delays can result in the EnumeratePrinters() call
taking a long time, making the Print Preview UI unresponsive.

There is a slightly different way to use the Print Spooler API that
does not block on network connections.  However, it does not return all
of the information of interest for Print Preview.  In particular, it is
missing the location metadata.

The location metadata can be found in the Windows registry for a
printer driver.  Since reading from the Windows registry also does not
block on network connections, using a combination of the two provides a
way to more quickly return all the same information as the prior Print
Spooler-only method.  Since the location metadata is relatively minor
(only shows up in the dialog to select a destination), using the
registry in this way is a relatively low-risk way to speed up the
enumeration, since all the other information is still acquired using
the Print Spooler API.

Introduce the ability to use a mixed method of using the registry to
supplement the faster Printer Spooler API method.  Make this approach
the default, but guard it behind a feature flag so that it can be
disabled with an emergency kill switch if necessary.

Given that this is a change to the low-level interaction with the
operating system, introduce a manual unit test to validate the new
method.

Also update the printing_features files to concatenate the nested
namespaces.

Bug: 374128404
Change-Id: Icc48e928dd484b916b2d5a130c39f5dc313ee35a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6220277
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Alan Screen <awscreen@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1427285}
This commit is contained in:
Alan Screen
2025-03-03 11:24:37 -08:00
committed by Chromium LUCI CQ
parent 456125bf00
commit 301d80b65b
11 changed files with 185 additions and 22 deletions

@ -7109,6 +7109,10 @@ const FeatureEntry kFeatureEntries[] = {
#endif // BUILDFLAG(IS_LINUX) ||BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_WIN)
{"fast-enumerate-printers", flag_descriptions::kFastEnumeratePrintersName,
flag_descriptions::kFastEnumeratePrintersDescription, kOsWin,
FEATURE_VALUE_TYPE(printing::features::kFastEnumeratePrinters)},
{"print-with-postscript-type42-fonts",
flag_descriptions::kPrintWithPostScriptType42FontsName,
flag_descriptions::kPrintWithPostScriptType42FontsDescription, kOsWin,

@ -4470,6 +4470,11 @@
],
"expiry_milestone": 138
},
{
"name": "fast-enumerate-printers",
"owners": [ "awscreen@chromium.org", "//printing/OWNERS" ],
"expiry_milestone": 140
},
{
"name": "fast-pair-debug-metadata",
"owners": [

@ -3228,6 +3228,12 @@ extern const char kLaunchWindowsNativeHostsDirectlyDescription[];
#endif // ENABLE_EXTENSIONS
#if BUILDFLAG(ENABLE_PRINTING)
inline constexpr char kFastEnumeratePrintersName[] =
"Use faster method for printer enumeration";
inline constexpr char kFastEnumeratePrintersDescription[] =
"When enumerating printers, use a faster method to acquire the basic "
"information for each printer.";
extern const char kPrintWithPostScriptType42FontsName[];
extern const char kPrintWithPostScriptType42FontsDescription[];

@ -6,6 +6,16 @@
#include <stddef.h>
#include <optional>
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "printing/backend/spooler_win.h"
#include "printing/backend/win_helper.h"
#include "printing/printing_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace printing {
@ -18,6 +28,56 @@ void DidLoadPrintableArea(size_t& counter) {
} // namespace
// This test makes use of a real print backend instance, and thus will
// interact with printer drivers installed on a system. This can be useful on
// machines which a developer has control over the driver installations, but is
// less useful on bots which are managed by the infra team. This test is
// intended to be run manually by developers using the --run-manual flag.
// Using the --v=1 flag will also display logging of all the printers in the
// enumeration list.
TEST(PrintBackendWinTest, MANUAL_ValidateFastEnumeratePrinters) {
// Disable the feature, which forces `EnumeratePrinters()` to use the legacy
// method to get the PrinterList result.
base::test::ScopedFeatureList scoped_disable;
scoped_disable.InitAndDisableFeature(features::kFastEnumeratePrinters);
// Get list of printer info using only the Print Spooler API.
const bool expect_some_printers =
internal::IsSpoolerRunning() == internal::SpoolerServiceStatus::kRunning;
VLOG(1) << "Print Spooler service is running: "
<< base::ToString(expect_some_printers);
const mojom::ResultCode expect_result = expect_some_printers
? mojom::ResultCode::kSuccess
: mojom::ResultCode::kFailed;
base::HistogramTester histogram_tester;
PrinterList printer_list;
auto backend = base::MakeRefCounted<PrintBackendWin>();
EXPECT_EQ(backend->EnumeratePrinters(printer_list), expect_result);
EXPECT_EQ(expect_some_printers, !printer_list.empty());
// For each printer, validate that faster method to get basic information
// using the mixed method of Print Spooler and the registry.
VLOG(1) << "Number of printers found: " << printer_list.size();
for (const auto& info_spooler_only : printer_list) {
VLOG(1) << "Found printer: `" << info_spooler_only.printer_name << "`";
ScopedPrinterHandle printer;
ASSERT_TRUE(printer.OpenPrinterWithName(
base::UTF8ToWide(info_spooler_only.printer_name).c_str()));
std::optional<PrinterBasicInfo> info_using_registry =
GetBasicPrinterInfoMixedMethodForTesting(printer.Get());
EXPECT_THAT(info_using_registry, testing::Optional(info_spooler_only));
}
EXPECT_THAT(
histogram_tester.GetAllSamples(
"Printing.EnumeratePrinters.BasicInfo.Registry"),
BucketsAre(base::Bucket(false, 0),
base::Bucket(true, static_cast<int>(printer_list.size()))));
}
TEST(PrintBackendWinTest, GetPrintableAreaSamePaper) {
auto backend = base::MakeRefCounted<PrintBackendWin>();
size_t load_printable_area_call_count = 0;

@ -59,6 +59,7 @@ class DriverInfo {
} // namespace internal
using PrinterInfo1 = internal::PrinterInfo<PRINTER_INFO_1, 1>;
using PrinterInfo2 = internal::PrinterInfo<PRINTER_INFO_2, 2>;
using PrinterInfo5 = internal::PrinterInfo<PRINTER_INFO_5, 5>;

@ -17,9 +17,11 @@
#include "base/file_version_info.h"
#include "base/files/file_path.h"
#include "base/memory/free_deleter.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
@ -30,6 +32,9 @@
#include "printing/backend/print_backend.h"
#include "printing/backend/print_backend_consts.h"
#include "printing/backend/printing_info_win.h"
#include "printing/printing_features.h"
namespace printing {
namespace {
@ -97,6 +102,23 @@ StartXpsPrintJobProc g_start_xps_print_job_proc = nullptr;
typedef std::string (*GetDisplayNameFunc)(const std::string& printer_name);
GetDisplayNameFunc g_get_display_name_func = nullptr;
PrinterBasicInfo InitializePrinterInfo(LPTSTR name, LPTSTR comment) {
PrinterBasicInfo printer_info;
printer_info.printer_name = base::WideToUTF8(name);
if (g_get_display_name_func) {
printer_info.display_name =
g_get_display_name_func(printer_info.printer_name);
} else {
printer_info.display_name = printer_info.printer_name;
}
if (comment) {
printer_info.printer_description = base::WideToUTF8(comment);
}
return printer_info;
}
HRESULT StreamFromPrintTicket(const std::string& print_ticket,
IStream** stream) {
DCHECK(stream);
@ -138,7 +160,8 @@ const char kXpsTicketMonochrome[] = "Monochrome";
constexpr wchar_t kDriversRegistryKeyPath[] =
L"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers\\";
// Registry value name for a port.
// Registry value names for info of a printer driver.
constexpr wchar_t kLocationRegistryValueName[] = L"Location";
constexpr wchar_t kPortRegistryValueName[] = L"Port";
// List of printer ports which are known to cause a UI dialog to be displayed
@ -177,9 +200,45 @@ std::string GetDriverVersionString(DWORDLONG version_number) {
static_cast<uint16_t>(version_number & 0xFFFF));
}
} // namespace
std::optional<PrinterBasicInfo> GetBasicPrinterInfoMixedMethod(HANDLE printer) {
// `printer` already guaranteed to be non-null by caller.
CHECK(printer);
PrinterInfo1 info_1;
if (!info_1.Init(printer)) {
return std::nullopt;
}
namespace printing {
PrinterBasicInfo printer_info =
InitializePrinterInfo(info_1.get()->pName, info_1.get()->pComment);
// Location is not available from the fast ::GetPrinter() call that uses
// PRINTER_INFO_1, it is only available with the potentially slow level
// PRINTER_INFO_2. Try to read the location information directly from the
// registry. Since the location metadata is not critical, do not fail if
// the printer cannot be found in the registry. Capture the success of this
// in a metric, as it will be of interest should finding the registry entry
// ever fail.
base::win::RegKey reg_key;
std::wstring root_key =
base::StrCat({kDriversRegistryKeyPath, info_1.get()->pName});
LONG result =
reg_key.Open(HKEY_LOCAL_MACHINE, root_key.c_str(), KEY_QUERY_VALUE);
base::UmaHistogramBoolean("Printing.EnumeratePrinters.BasicInfo.Registry",
result == ERROR_SUCCESS);
if (result == ERROR_SUCCESS) {
// Even though the registry entry for the printer is found, it isn't
// required to contain a value name for location.
std::wstring value_data;
result = reg_key.ReadValue(kLocationRegistryValueName, &value_data);
if (result == ERROR_SUCCESS && !value_data.empty()) {
printer_info.options[kLocationTagName] = base::WideToUTF8(value_data);
}
}
return printer_info;
}
} // namespace
// static
bool PrinterHandleTraits::CloseHandle(HANDLE handle) {
@ -396,22 +455,18 @@ std::optional<PrinterBasicInfo> GetBasicPrinterInfo(HANDLE printer) {
return std::nullopt;
}
if (base::FeatureList::IsEnabled(features::kFastEnumeratePrinters)) {
return GetBasicPrinterInfoMixedMethod(printer);
}
PrinterInfo2 info_2;
if (!info_2.Init(printer)) {
return std::nullopt;
}
PrinterBasicInfo printer_info;
printer_info.printer_name = base::WideToUTF8(info_2.get()->pPrinterName);
if (g_get_display_name_func) {
printer_info.display_name =
g_get_display_name_func(printer_info.printer_name);
} else {
printer_info.display_name = printer_info.printer_name;
}
if (info_2.get()->pComment) {
printer_info.printer_description = base::WideToUTF8(info_2.get()->pComment);
}
PrinterBasicInfo printer_info =
InitializePrinterInfo(info_2.get()->pPrinterName, info_2.get()->pComment);
if (info_2.get()->pLocation) {
std::string location = base::WideToUTF8(info_2.get()->pLocation);
if (!location.empty()) {
@ -421,6 +476,12 @@ std::optional<PrinterBasicInfo> GetBasicPrinterInfo(HANDLE printer) {
return printer_info;
}
std::optional<PrinterBasicInfo>
GetBasicPrinterInfoMixedMethodForTesting( // IN-TEST
HANDLE printer) {
return GetBasicPrinterInfoMixedMethod(printer);
}
std::vector<std::string> GetDriverInfo(HANDLE printer) {
DCHECK(printer);
std::vector<std::string> driver_info;

@ -163,6 +163,12 @@ void SetGetDisplayNameFunction(
COMPONENT_EXPORT(PRINT_BACKEND)
std::optional<PrinterBasicInfo> GetBasicPrinterInfo(HANDLE printer);
// Helper to read printer info using both Print Spooler API and the registry.
// Possibly called by `GetBasicPrinterInfo()`. Exported to support testing.
COMPONENT_EXPORT(PRINT_BACKEND)
std::optional<PrinterBasicInfo> GetBasicPrinterInfoMixedMethodForTesting(
HANDLE printer);
COMPONENT_EXPORT(PRINT_BACKEND)
std::vector<std::string> GetDriverInfo(HANDLE printer);

@ -11,8 +11,7 @@
#include "base/metrics/field_trial_params.h"
#endif
namespace printing {
namespace features {
namespace printing::features {
#if BUILDFLAG(IS_CHROMEOS)
// Add printers via printscanmgr instead of debugd.
@ -35,6 +34,13 @@ BASE_FEATURE(kCupsIppPrintingBackend,
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_WIN)
// Use a faster method to enumerate printers, using a combination of a
// non-blocking Print Spooler API and the Windows registry to speed up reading
// of basic printer info.
BASE_FEATURE(kFastEnumeratePrinters,
"FastEnumeratePrinters",
base::FEATURE_ENABLED_BY_DEFAULT);
// When using PostScript level 3 printing, render text with Type 42 fonts if
// possible.
BASE_FEATURE(kPrintWithPostScriptType42Fonts,
@ -91,5 +97,4 @@ const base::FeatureParam<bool> kEnableOopPrintDriversSingleProcess{
#endif
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
} // namespace features
} // namespace printing
} // namespace printing::features

@ -11,8 +11,7 @@
#include "build/build_config.h"
#include "printing/buildflags/buildflags.h"
namespace printing {
namespace features {
namespace printing::features {
// The following features are declared alphabetically. The features should be
// documented with descriptions of their behaviors in the .cc file.
@ -28,6 +27,8 @@ COMPONENT_EXPORT(PRINTING_BASE) BASE_DECLARE_FEATURE(kCupsIppPrintingBackend);
#if BUILDFLAG(IS_WIN)
COMPONENT_EXPORT(PRINTING_BASE)
BASE_DECLARE_FEATURE(kFastEnumeratePrinters);
COMPONENT_EXPORT(PRINTING_BASE)
BASE_DECLARE_FEATURE(kPrintWithPostScriptType42Fonts);
COMPONENT_EXPORT(PRINTING_BASE)
BASE_DECLARE_FEATURE(kPrintWithReducedRasterization);
@ -51,7 +52,6 @@ extern const base::FeatureParam<bool> kEnableOopPrintDriversSingleProcess;
#endif
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
} // namespace features
} // namespace printing
} // namespace printing::features
#endif // PRINTING_PRINTING_FEATURES_H_

@ -8900,6 +8900,7 @@ from previous Chrome versions.
<int value="-2097013050" label="WaylandTextInputV3:disabled"/>
<int value="-2096845406" label="QuickAnswersRichCard:enabled"/>
<int value="-2095519429" label="FtpProtocol:enabled"/>
<int value="-2095123598" label="FastEnumeratePrinters:disabled"/>
<int value="-2094897448" label="TranslateIntent:disabled"/>
<int value="-2093968325" label="FedCmAutoSelectedFlag:disabled"/>
<int value="-2093047873"
@ -11672,6 +11673,7 @@ from previous Chrome versions.
<int value="-1018983714" label="FedCm:enabled"/>
<int value="-1018454657" label="SharingPeerConnectionReceiver:enabled"/>
<int value="-1017686690" label="CrosMall:enabled"/>
<int value="-1017396114" label="FastEnumeratePrinters:enabled"/>
<int value="-1016669222" label="CloudPrinterHandler:enabled"/>
<int value="-1016202433" label="disable-add-to-shelf"/>
<int value="-1016196171"

@ -477,6 +477,19 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
<histogram name="Printing.EnumeratePrinters.BasicInfo.Registry"
enum="BooleanSuccess" expires_after="2025-07-27">
<owner>awscreen@chromium.org</owner>
<owner>thestig@chromium.org</owner>
<summary>
When enumerating the list of printers on Windows, records an instance for
using the Windows registry to fill in extra printer details that are
unavailable from the fast Print Spooler API call. Each instance is
successful only if a registry entry for a printer is located that
corresponds with the printer returned by the Print Spooler API.
</summary>
</histogram>
<histogram name="Printing.Oop.PrintResult" enum="PrintOopResult"
expires_after="2025-08-24">
<owner>awscreen@chromium.org</owner>