feat: allow defaulting to printer default page size (#49523)

Co-authored-by: Edvan de Matos <edvan.santos@querodelivery.com>
This commit is contained in:
Shelley Vohr
2026-02-13 13:40:14 +01:00
committed by GitHub
parent 3a5f9e0a33
commit dcdbb0397e
7 changed files with 162 additions and 65 deletions

View File

@@ -1748,11 +1748,12 @@ Returns `Promise<PrinterInfo[]>` - Resolves with a [`PrinterInfo[]`](structures/
* `footer` string (optional) - string to be printed as page footer.
* `pageSize` string | Size (optional) - Specify page size of the printed document. Can be `A0`, `A1`, `A2`, `A3`,
`A4`, `A5`, `A6`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` and `width`.
* `usePrinterDefaultPageSize` boolean (optional) - Whether to use a given printer's default page size. Default is `false`. Cannot be combined with `pageSize`. When `deviceName` is provided, uses the default page size of that specific printer. When `deviceName` is not provided, uses the default page size of the system's default printer. If the printer's default page size cannot be retrieved, falls back to A4 (210mm x 297mm).
* `callback` Function (optional)
* `success` boolean - Indicates success of the print call.
* `failureReason` string - Error description called back if the print fails.
When a custom `pageSize` is passed, Chromium attempts to validate platform specific minimum values for `width_microns` and `height_microns`. Width and height must both be minimum 353 microns but may be higher on some operating systems.
When a custom `pageSize` is passed, Chromium attempts to validate platform specific minimum values for `width_microns` and `height_microns`. Width and height must both be minimum 353 microns but may be higher on some operating systems. If a valid `pageSize` is not passed and `usePrinterDefaultPageSize` is `false`, an error will be thrown.
Prints window's web page. When `silent` is set to `true`, Electron will pick
the system's default printer if `deviceName` is empty and the default settings for printing.

View File

@@ -588,6 +588,7 @@ Stops any `findInPage` request for the `webview` with the provided `action`.
* `footer` string (optional) - string to be printed as page footer.
* `pageSize` string | Size (optional) - Specify page size of the printed document. Can be `A3`,
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` in microns.
* `usePrinterDefaultPageSize` boolean (optional) - Whether to use the system's default page size. Default is `false`. Cannot be combined with `pageSize`. When `deviceName` is provided, uses the default page size of that specific printer. When `deviceName` is not provided, uses the default page size of the system's default printer. If the printer's default page size cannot be retrieved, falls back to A4 (210mm x 297mm).
Returns `Promise<void>`

View File

@@ -263,7 +263,12 @@ WebContents.prototype.print = function (options: ElectronInternal.WebContentsPri
throw new TypeError('webContents.print(): Invalid print settings specified.');
}
const { pageSize } = options;
const { pageSize, usePrinterDefaultPageSize } = options;
if (usePrinterDefaultPageSize !== undefined && pageSize !== undefined) {
throw new Error('usePrinterDefaultPageSize cannot be combined with pageSize');
}
if (typeof pageSize === 'string' && PDFPageSizes[pageSize]) {
const mediaSize = PDFPageSizes[pageSize];
options.mediaSize = {

View File

@@ -427,6 +427,36 @@ namespace {
// Global toggle for disabling draggable regions checks.
bool g_disable_draggable_regions = false;
// Constants we use for printing.
constexpr char kFrom[] = "from";
constexpr char kTo[] = "to";
constexpr char kUseDefaultPrinterPageSize[] = "usePrinterDefaultPageSize";
constexpr char kSilent[] = "silent";
constexpr char kHeader[] = "header";
constexpr char kFooter[] = "footer";
constexpr char kPageRanges[] = "pageRanges";
constexpr char kMediaSize[] = "mediaSize";
constexpr char kDpi[] = "dpi";
constexpr char kMarginType[] = "marginType";
constexpr char kMargins[] = "margins";
// Constants we use for printToPDF options.
constexpr char kLandscape[] = "landscape";
constexpr char kDisplayHeaderFooter[] = "displayHeaderFooter";
constexpr char kPrintBackground[] = "printBackground";
constexpr char kScale[] = "scale";
constexpr char kPaperWidth[] = "paperWidth";
constexpr char kPaperHeight[] = "paperHeight";
constexpr char kMarginTop[] = "marginTop";
constexpr char kMarginBottom[] = "marginBottom";
constexpr char kMarginLeft[] = "marginLeft";
constexpr char kMarginRight[] = "marginRight";
constexpr char kHeaderTemplate[] = "headerTemplate";
constexpr char kFooterTemplate[] = "footerTemplate";
constexpr char kPreferCSSPageSize[] = "preferCSSPageSize";
constexpr char kGenerateTaggedPDF[] = "generateTaggedPDF";
constexpr char kGenerateDocumentOutline[] = "generateDocumentOutline";
constexpr std::string_view CursorTypeToString(
ui::mojom::CursorType cursor_type) {
switch (cursor_type) {
@@ -3052,6 +3082,28 @@ void OnGetDeviceNameToUse(base::WeakPtr<content::WebContents> web_contents,
print_settings.Set(printing::kSettingDpiVertical, dpi.height());
}
auto make_media_size = [](int height_microns, int width_microns) {
return base::DictValue()
.Set(printing::kSettingMediaSizeHeightMicrons, height_microns)
.Set(printing::kSettingMediaSizeWidthMicrons, width_microns)
.Set(printing::kSettingsImageableAreaLeftMicrons, 0)
.Set(printing::kSettingsImageableAreaTopMicrons, height_microns)
.Set(printing::kSettingsImageableAreaRightMicrons, width_microns)
.Set(printing::kSettingsImageableAreaBottomMicrons, 0)
.Set(printing::kSettingMediaSizeIsDefault, true);
};
const bool use_default_size =
print_settings.FindBool(kUseDefaultPrinterPageSize).value_or(false);
std::optional<gfx::Size> paper_size;
if (use_default_size)
paper_size = GetPrinterDefaultPaperSize(base::UTF16ToUTF8(info.second));
print_settings.Set(
printing::kSettingMediaSize,
paper_size ? make_media_size(paper_size->height(), paper_size->width())
: make_media_size(297000, 210000));
content::RenderFrameHost* rfh = GetRenderFrameHostToUse(web_contents.get());
if (!rfh)
return;
@@ -3121,30 +3173,32 @@ void WebContents::Print(gin::Arguments* const args) {
}
// Set optional silent printing.
settings.Set("silent", options.ValueOrDefault("silent", false));
settings.Set(kSilent, options.ValueOrDefault(kSilent, false));
settings.Set(printing::kSettingShouldPrintBackgrounds,
options.ValueOrDefault("printBackground", false));
settings.Set(
printing::kSettingShouldPrintBackgrounds,
options.ValueOrDefault(printing::kSettingShouldPrintBackgrounds, false));
// Set custom margin settings
auto margins = gin_helper::Dictionary::CreateEmpty(isolate);
if (options.Get("margins", &margins)) {
if (options.Get(kMargins, &margins)) {
printing::mojom::MarginType margin_type =
printing::mojom::MarginType::kDefaultMargins;
margins.Get("marginType", &margin_type);
margins.Get(kMarginType, &margin_type);
settings.Set(printing::kSettingMarginsType, static_cast<int>(margin_type));
if (margin_type == printing::mojom::MarginType::kCustomMargins) {
settings.Set(printing::kSettingMarginsCustom,
base::DictValue{}
.Set(printing::kSettingMarginTop,
margins.ValueOrDefault("top", 0))
.Set(printing::kSettingMarginBottom,
margins.ValueOrDefault("bottom", 0))
.Set(printing::kSettingMarginLeft,
margins.ValueOrDefault("left", 0))
.Set(printing::kSettingMarginRight,
margins.ValueOrDefault("right", 0)));
settings.Set(
printing::kSettingMarginsCustom,
base::DictValue{}
.Set(printing::kSettingMarginTop,
margins.ValueOrDefault(printing::kSettingMarginTop, 0))
.Set(printing::kSettingMarginBottom,
margins.ValueOrDefault(printing::kSettingMarginBottom, 0))
.Set(printing::kSettingMarginLeft,
margins.ValueOrDefault(printing::kSettingMarginLeft, 0))
.Set(printing::kSettingMarginRight,
margins.ValueOrDefault(printing::kSettingMarginRight, 0)));
}
} else {
settings.Set(
@@ -3153,37 +3207,42 @@ void WebContents::Print(gin::Arguments* const args) {
}
// Set whether to print color or greyscale
settings.Set(printing::kSettingColor,
static_cast<int>(options.ValueOrDefault("color", true)
? printing::mojom::ColorModel::kColor
: printing::mojom::ColorModel::kGray));
settings.Set(
printing::kSettingColor,
static_cast<int>(options.ValueOrDefault(printing::kSettingColor, true)
? printing::mojom::ColorModel::kColor
: printing::mojom::ColorModel::kGray));
// Is the orientation landscape or portrait.
settings.Set(printing::kSettingLandscape,
options.ValueOrDefault("landscape", false));
options.ValueOrDefault(printing::kSettingLandscape, false));
// We set the default to the system's default printer and only update
// if at the Chromium level if the user overrides.
// Printer device name as opened by the OS.
const auto device_name =
options.ValueOrDefault("deviceName", std::u16string{});
options.ValueOrDefault(printing::kSettingDeviceName, std::u16string{});
settings.Set(printing::kSettingScaleFactor,
options.ValueOrDefault("scaleFactor", 100));
options.ValueOrDefault(printing::kSettingScaleFactor, 100));
settings.Set(printing::kSettingPagesPerSheet,
options.ValueOrDefault("pagesPerSheet", 1));
options.ValueOrDefault(printing::kSettingPagesPerSheet, 1));
// True if the user wants to print with collate.
settings.Set(printing::kSettingCollate,
options.ValueOrDefault("collate", true));
options.ValueOrDefault(printing::kSettingCollate, true));
// True if the user wants to print using the printer's default page size.
settings.Set(kUseDefaultPrinterPageSize,
options.ValueOrDefault(kUseDefaultPrinterPageSize, false));
// The number of individual copies to print
settings.Set(printing::kSettingCopies, options.ValueOrDefault("copies", 1));
settings.Set(printing::kSettingCopies,
options.ValueOrDefault(printing::kSettingCopies, 1));
// Strings to be printed as headers and footers if requested by the user.
const auto header = options.ValueOrDefault("header", std::string{});
const auto footer = options.ValueOrDefault("footer", std::string{});
const auto header = options.ValueOrDefault(kHeader, std::string{});
const auto footer = options.ValueOrDefault(kFooter, std::string{});
if (!(header.empty() && footer.empty())) {
settings.Set(printing::kSettingHeaderFooterEnabled, true);
@@ -3203,11 +3262,11 @@ void WebContents::Print(gin::Arguments* const args) {
// Set custom page ranges to print
std::vector<gin_helper::Dictionary> page_ranges;
if (options.Get("pageRanges", &page_ranges)) {
if (options.Get(kPageRanges, &page_ranges)) {
base::ListValue page_range_list;
for (auto& range : page_ranges) {
int from, to;
if (range.Get("from", &from) && range.Get("to", &to)) {
if (range.Get(kFrom, &from) && range.Get(kTo, &to)) {
base::DictValue range_dict;
// Chromium uses 1-based page ranges, so increment each by 1.
range_dict.Set(printing::kSettingPageRangeFrom, from + 1);
@@ -3223,31 +3282,22 @@ void WebContents::Print(gin::Arguments* const args) {
// Duplex type user wants to use.
const auto duplex_mode = options.ValueOrDefault(
"duplexMode", printing::mojom::DuplexMode::kSimplex);
printing::kSettingDuplexMode, printing::mojom::DuplexMode::kSimplex);
settings.Set(printing::kSettingDuplexMode, static_cast<int>(duplex_mode));
// Set custom media size if passed. If none is passed, the media size
// will be set in OnGetDeviceNameToUse based on the printer's default
// settings where applicable.
base::DictValue media_size;
if (options.Get("mediaSize", &media_size)) {
if (options.Get(kMediaSize, &media_size))
settings.Set(printing::kSettingMediaSize, std::move(media_size));
} else {
// Default to A4 paper size (210mm x 297mm)
settings.Set(printing::kSettingMediaSize,
base::DictValue()
.Set(printing::kSettingMediaSizeHeightMicrons, 297000)
.Set(printing::kSettingMediaSizeWidthMicrons, 210000)
.Set(printing::kSettingsImageableAreaLeftMicrons, 0)
.Set(printing::kSettingsImageableAreaTopMicrons, 297000)
.Set(printing::kSettingsImageableAreaRightMicrons, 210000)
.Set(printing::kSettingsImageableAreaBottomMicrons, 0)
.Set(printing::kSettingMediaSizeIsDefault, true));
}
// Set custom dots per inch (dpi)
if (gin_helper::Dictionary dpi; options.Get("dpi", &dpi)) {
if (gin_helper::Dictionary dpi; options.Get(kDpi, &dpi)) {
settings.Set(printing::kSettingDpiHorizontal,
dpi.ValueOrDefault("horizontal", 72));
dpi.ValueOrDefault(printing::kSettingDpiHorizontal, 72));
settings.Set(printing::kSettingDpiVertical,
dpi.ValueOrDefault("vertical", 72));
dpi.ValueOrDefault(printing::kSettingDpiVertical, 72));
}
print_task_runner_->PostTaskAndReplyWithResult(
@@ -3265,24 +3315,24 @@ v8::Local<v8::Promise> WebContents::PrintToPDF(const base::Value& settings) {
// This allows us to track headless printing calls.
auto unique_id = settings.GetDict().FindInt(printing::kPreviewRequestID);
auto landscape = settings.GetDict().FindBool("landscape");
auto landscape = settings.GetDict().FindBool(kLandscape);
auto display_header_footer =
settings.GetDict().FindBool("displayHeaderFooter");
auto print_background = settings.GetDict().FindBool("printBackground");
auto scale = settings.GetDict().FindDouble("scale");
auto paper_width = settings.GetDict().FindDouble("paperWidth");
auto paper_height = settings.GetDict().FindDouble("paperHeight");
auto margin_top = settings.GetDict().FindDouble("marginTop");
auto margin_bottom = settings.GetDict().FindDouble("marginBottom");
auto margin_left = settings.GetDict().FindDouble("marginLeft");
auto margin_right = settings.GetDict().FindDouble("marginRight");
auto page_ranges = *settings.GetDict().FindString("pageRanges");
auto header_template = *settings.GetDict().FindString("headerTemplate");
auto footer_template = *settings.GetDict().FindString("footerTemplate");
auto prefer_css_page_size = settings.GetDict().FindBool("preferCSSPageSize");
auto generate_tagged_pdf = settings.GetDict().FindBool("generateTaggedPDF");
settings.GetDict().FindBool(kDisplayHeaderFooter);
auto print_background = settings.GetDict().FindBool(kPrintBackground);
auto scale = settings.GetDict().FindDouble(kScale);
auto paper_width = settings.GetDict().FindDouble(kPaperWidth);
auto paper_height = settings.GetDict().FindDouble(kPaperHeight);
auto margin_top = settings.GetDict().FindDouble(kMarginTop);
auto margin_bottom = settings.GetDict().FindDouble(kMarginBottom);
auto margin_left = settings.GetDict().FindDouble(kMarginLeft);
auto margin_right = settings.GetDict().FindDouble(kMarginRight);
auto page_ranges = *settings.GetDict().FindString(kPageRanges);
auto header_template = *settings.GetDict().FindString(kHeaderTemplate);
auto footer_template = *settings.GetDict().FindString(kFooterTemplate);
auto prefer_css_page_size = settings.GetDict().FindBool(kPreferCSSPageSize);
auto generate_tagged_pdf = settings.GetDict().FindBool(kGenerateTaggedPDF);
auto generate_document_outline =
settings.GetDict().FindBool("generateDocumentOutline");
settings.GetDict().FindBool(kGenerateDocumentOutline);
content::RenderFrameHost* rfh = GetRenderFrameHostToUse(web_contents());
absl::variant<printing::mojom::PrintPagesParamsPtr, std::string>

View File

@@ -191,4 +191,33 @@ scoped_refptr<base::TaskRunner> CreatePrinterHandlerTaskRunner() {
#endif
}
std::optional<gfx::Size> GetPrinterDefaultPaperSize(
const std::string& printer_name) {
#if BUILDFLAG(IS_WIN)
// Blocking is needed here because Windows printer drivers are oftentimes
// not thread-safe and have to be accessed on the UI thread.
ScopedAllowBlockingForElectron allow_blocking;
#endif
if (printer_name.empty())
return std::nullopt;
scoped_refptr<printing::PrintBackend> print_backend =
printing::PrintBackend::CreateInstance(
g_browser_process->GetApplicationLocale());
if (!print_backend)
return std::nullopt;
printing::PrinterSemanticCapsAndDefaults caps;
printing::mojom::ResultCode result =
print_backend->GetPrinterSemanticCapsAndDefaults(printer_name, &caps);
if (result != printing::mojom::ResultCode::kSuccess)
return std::nullopt;
if (!caps.default_paper.size_um().IsEmpty())
return caps.default_paper.size_um();
return std::nullopt;
}
} // namespace electron

View File

@@ -44,6 +44,11 @@ std::pair<std::string, std::u16string> GetDeviceNameToUse(
// This function creates a task runner for use with printing tasks.
scoped_refptr<base::TaskRunner> CreatePrinterHandlerTaskRunner();
// This function returns the default paper size of the specified printer, if
// available.
std::optional<gfx::Size> GetPrinterDefaultPaperSize(
const std::string& printer_name);
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_

View File

@@ -273,6 +273,12 @@ describe('webContents module', () => {
}).to.throw(`Unsupported pageSize: ${badSize}`);
});
it('throws when a user passes both pageSize and usePrinterDefaultPageSize', () => {
expect(() => {
w.webContents.print({ pageSize: 'A4', usePrinterDefaultPageSize: true });
}).to.throw('usePrinterDefaultPageSize cannot be combined with pageSize');
});
it('throws when an invalid callback is passed', () => {
expect(() => {
// @ts-ignore this line is intentionally incorrect