diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 90055dfc4f..123ba534ab 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1748,11 +1748,12 @@ Returns `Promise` - 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. diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 24d6493679..409cc71e9e 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -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` diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index dde4432a6d..2c6937d8ae 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -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 = { diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 3460e5f005..0e3ca6e4dd 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -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 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 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(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(options.ValueOrDefault("color", true) - ? printing::mojom::ColorModel::kColor - : printing::mojom::ColorModel::kGray)); + settings.Set( + printing::kSettingColor, + static_cast(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 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(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 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 diff --git a/shell/browser/printing/printing_utils.cc b/shell/browser/printing/printing_utils.cc index f8e4583630..e1df0543ae 100644 --- a/shell/browser/printing/printing_utils.cc +++ b/shell/browser/printing/printing_utils.cc @@ -191,4 +191,33 @@ scoped_refptr CreatePrinterHandlerTaskRunner() { #endif } +std::optional 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 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 diff --git a/shell/browser/printing/printing_utils.h b/shell/browser/printing/printing_utils.h index e795aeecec..e6f486117c 100644 --- a/shell/browser/printing/printing_utils.h +++ b/shell/browser/printing/printing_utils.h @@ -44,6 +44,11 @@ std::pair GetDeviceNameToUse( // This function creates a task runner for use with printing tasks. scoped_refptr CreatePrinterHandlerTaskRunner(); +// This function returns the default paper size of the specified printer, if +// available. +std::optional GetPrinterDefaultPaperSize( + const std::string& printer_name); + } // namespace electron #endif // ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_ diff --git a/spec/api-web-contents-spec.ts b/spec/api-web-contents-spec.ts index c587a795a1..96351b1772 100644 --- a/spec/api-web-contents-spec.ts +++ b/spec/api-web-contents-spec.ts @@ -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