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:

committed by
Chromium LUCI CQ

parent
456125bf00
commit
301d80b65b
@ -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>
|
||||
|
Reference in New Issue
Block a user