diff --git a/BUILD.gn b/BUILD.gn index 107f317be548f..5c5f4d27e01e6 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -667,8 +667,6 @@ source_set("electron_lib") { if (enable_basic_printing) { sources += [ - "shell/browser/printing/print_preview_message_handler.cc", - "shell/browser/printing/print_preview_message_handler.h", "shell/browser/printing/print_view_manager_electron.cc", "shell/browser/printing/print_view_manager_electron.h", "shell/renderer/printing/print_render_frame_helper_delegate.cc", diff --git a/chromium_src/BUILD.gn b/chromium_src/BUILD.gn index 41aad0ed3b942..f5a8939902246 100644 --- a/chromium_src/BUILD.gn +++ b/chromium_src/BUILD.gn @@ -216,6 +216,8 @@ static_library("chrome") { "//chrome/browser/printing/printer_query.h", "//chrome/browser/printing/printing_service.cc", "//chrome/browser/printing/printing_service.h", + "//components/printing/browser/print_to_pdf/pdf_print_utils.cc", + "//components/printing/browser/print_to_pdf/pdf_print_utils.h", ] if (enable_oop_printing) { diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 48fc8c21efae3..23a0a41d43cb2 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1428,7 +1428,7 @@ Returns `Promise` - Resolves with a [`PrinterInfo[]`](structures/ * `header` string (optional) - string to be printed as page header. * `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`. + `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` and `width`. * `callback` Function (optional) * `success` boolean - Indicates success of the print call. * `failureReason` string - Error description called back if the print fails. @@ -1459,43 +1459,28 @@ win.webContents.print(options, (success, errorType) => { #### `contents.printToPDF(options)` * `options` Object - * `headerFooter` Record (optional) - the header and footer for the PDF. - * `title` string - The title for the PDF header. - * `url` string - the url for the PDF footer. - * `landscape` boolean (optional) - `true` for landscape, `false` for portrait. - * `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for - default margin, 1 for no margin, and 2 for minimum margin. - * `scaleFactor` number (optional) - The scale factor of the web page. Can range from 0 to 100. - * `pageRanges` Record (optional) - The page range to print. - * `from` number - Index of the first page to print (0-based). - * `to` number - Index of the last page to print (inclusive) (0-based). - * `pageSize` string | Size (optional) - Specify page size of the generated PDF. Can be `A3`, - `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` and `width` in microns. - * `printBackground` boolean (optional) - Whether to print CSS backgrounds. - * `printSelectionOnly` boolean (optional) - Whether to print selection only. + * `landscape` boolean (optional) - Paper orientation.`true` for landscape, `false` for portrait. Defaults to false. + * `displayHeaderFooter` boolean (optional) - Whether to display header and footer. Defaults to false. + * `printBackground` boolean (optional) - Whether to print background graphics. Defaults to false. + * `scale` number(optional) - Scale of the webpage rendering. Defaults to 1. + * `pageSize` string | Size (optional) - Specify page size of the generated PDF. Can be `A0`, `A1`, `A2`, `A3`, + `A4`, `A5`, `A6`, `Legal`, `Letter`, `Tabloid`, `Ledger`, or an Object containing `height` and `width` in inches. Defaults to `Letter`. + * `margins` Object (optional) + * `top` number (optional) - Top margin in inches. Defaults to 1cm (~0.4 inches). + * `bottom` number (optional) - Bottom margin in inches. Defaults to 1cm (~0.4 inches). + * `left` number (optional) - Left margin in inches. Defaults to 1cm (~0.4 inches). + * `right` number (optional) - Right margin in inches. Defaults to 1cm (~0.4 inches). + * `pageRanges` string (optional) - Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means print all pages. + * `headerTemplate` string (optional) - HTML template for the print header. Should be valid HTML markup with following classes used to inject printing values into them: `date` (formatted print date), `title` (document title), `url` (document location), `pageNumber` (current page number) and `totalPages` (total pages in the document). For example, `` would generate span containing the title. + * `footerTemplate` string (optional) - HTML template for the print footer. Should use the same format as the `headerTemplate`. + * `preferCSSPageSize` boolean (optional) - Whether or not to prefer page size as defined by css. Defaults to false, in which case the content will be scaled to fit the paper size. Returns `Promise` - Resolves with the generated PDF data. -Prints window's web page as PDF with Chromium's preview printing custom -settings. +Prints the window's web page as PDF. The `landscape` will be ignored if `@page` CSS at-rule is used in the web page. -By default, an empty `options` will be regarded as: - -```javascript -{ - marginsType: 0, - printBackground: false, - printSelectionOnly: false, - landscape: false, - pageSize: 'A4', - scaleFactor: 100 -} -``` - -Use `page-break-before: always;` CSS style to force to print to a new page. - An example of `webContents.printToPDF`: ```javascript @@ -1504,7 +1489,7 @@ const fs = require('fs') const path = require('path') const os = require('os') -const win = new BrowserWindow({ width: 800, height: 600 }) +const win = new BrowserWindow() win.loadURL('http://github.com') win.webContents.on('did-finish-load', () => { @@ -1521,6 +1506,8 @@ win.webContents.on('did-finish-load', () => { }) ``` +See [Page.printToPdf](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF) for more information. + #### `contents.addWorkSpace(path)` * `path` string diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 865b101168ad6..cc86a05d5be4d 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -565,21 +565,21 @@ Prints `webview`'s web page. Same as `webContents.print([options])`. ### `.printToPDF(options)` * `options` Object - * `headerFooter` Record (optional) - the header and footer for the PDF. - * `title` string - The title for the PDF header. - * `url` string - the url for the PDF footer. - * `landscape` boolean (optional) - `true` for landscape, `false` for portrait. - * `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for - default margin, 1 for no margin, and 2 for minimum margin. - and `width` in microns. - * `scaleFactor` number (optional) - The scale factor of the web page. Can range from 0 to 100. - * `pageRanges` Record (optional) - The page range to print. On macOS, only the first range is honored. - * `from` number - Index of the first page to print (0-based). - * `to` number - Index of the last page to print (inclusive) (0-based). - * `pageSize` string | Size (optional) - Specify page size of the generated PDF. Can be `A3`, - `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` - * `printBackground` boolean (optional) - Whether to print CSS backgrounds. - * `printSelectionOnly` boolean (optional) - Whether to print selection only. + * `landscape` boolean (optional) - Paper orientation.`true` for landscape, `false` for portrait. Defaults to false. + * `displayHeaderFooter` boolean (optional) - Whether to display header and footer. Defaults to false. + * `printBackground` boolean (optional) - Whether to print background graphics. Defaults to false. + * `scale` number(optional) - Scale of the webpage rendering. Defaults to 1. + * `pageSize` string | Size (optional) - Specify page size of the generated PDF. Can be `A0`, `A1`, `A2`, `A3`, + `A4`, `A5`, `A6`, `Legal`, `Letter`, `Tabloid`, `Ledger`, or an Object containing `height` and `width` in inches. Defaults to `Letter`. + * `margins` Object (optional) + * `top` number (optional) - Top margin in inches. Defaults to 1cm (~0.4 inches). + * `bottom` number (optional) - Bottom margin in inches. Defaults to 1cm (~0.4 inches). + * `left` number (optional) - Left margin in inches. Defaults to 1cm (~0.4 inches). + * `right` number (optional) - Right margin in inches. Defaults to 1cm (~0.4 inches). + * `pageRanges` string (optional) - Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means print all pages. + * `headerTemplate` string (optional) - HTML template for the print header. Should be valid HTML markup with following classes used to inject printing values into them: `date` (formatted print date), `title` (document title), `url` (document location), `pageNumber` (current page number) and `totalPages` (total pages in the document). For example, `` would generate span containing the title. + * `footerTemplate` string (optional) - HTML template for the print footer. Should use the same format as the `headerTemplate`. + * `preferCSSPageSize` boolean (optional) - Whether or not to prefer page size as defined by css. Defaults to false, in which case the content will be scaled to fit the paper size. Returns `Promise` - Resolves with the generated PDF data. diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index cf0e48d0df670..e0dea89dfd9a2 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -14,6 +14,61 @@ This document uses the following convention to categorize breaking changes: ## Planned Breaking API Changes (20.0) +### API Changed: `webContents.printToPDF()` + +`webContents.printToPDF()` has been modified to conform to [`Page.printToPDF`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF) in the Chrome DevTools Protocol. This has been changes in order to +address changes upstream that made our previous implementation untenable and rife with bugs. + +**Arguments Changed** + +* `pageRanges` + +**Arguments Removed** + +* `printSelectionOnly` +* `marginsType` +* `headerFooter` +* `scaleFactor` + +**Arguments Added** + +* `headerTemplate` +* `footerTemplate` +* `displayHeaderFooter` +* `margins` +* `scale` +* `preferCSSPageSize` + +```js +// Main process +const { webContents } = require('electron') + +webContents.printToPDF({ + landscape: true, + displayHeaderFooter: true, + printBackground: true, + scale: 2, + pageSize: 'Ledger', + margins: { + top: 2, + bottom: 2, + left: 2, + right: 2 + }, + pageRanges: '1-5, 8, 11-13', + headerTemplate: '

Title

', + footerTemplate: '
', + preferCSSPageSize: true +}).then(data => { + fs.writeFile(pdfPath, data, (error) => { + if (error) throw error + console.log(`Wrote PDF successfully to ${pdfPath}`) + }) +}).catch(error => { + console.log(`Failed to write PDF to ${pdfPath}: `, error) +}) +``` + ### Default Changed: renderers without `nodeIntegration: true` are sandboxed by default Previously, renderers that specified a preload script defaulted to being diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index 0f2091f177de5..1b079694b7207 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -63,6 +63,20 @@ const PDFPageSizes: Record = { } } as const; +const paperFormats: Record = { + letter: { width: 8.5, height: 11 }, + legal: { width: 8.5, height: 14 }, + tabloid: { width: 11, height: 17 }, + ledger: { width: 17, height: 11 }, + a0: { width: 33.1, height: 46.8 }, + a1: { width: 23.4, height: 33.1 }, + a2: { width: 16.54, height: 23.4 }, + a3: { width: 11.7, height: 16.54 }, + a4: { width: 8.27, height: 11.7 }, + a5: { width: 5.83, height: 8.27 }, + a6: { width: 4.13, height: 5.83 } +} as const; + // The minimum micron size Chromium accepts is that where: // Per printing/units.h: // * kMicronsPerInch - Length of an inch in 0.001mm unit. @@ -76,42 +90,6 @@ const isValidCustomPageSize = (width: number, height: number) => { return [width, height].every(x => x > 352); }; -// Default printing setting -const defaultPrintingSetting = { - // Customizable. - pageRange: [] as {from: number, to: number}[], - mediaSize: {} as ElectronInternal.MediaSize, - landscape: false, - headerFooterEnabled: false, - marginsType: 0, - scaleFactor: 100, - shouldPrintBackgrounds: false, - shouldPrintSelectionOnly: false, - // Non-customizable. - printWithCloudPrint: false, - printWithPrivet: false, - printWithExtension: false, - pagesPerSheet: 1, - isFirstRequest: false, - previewUIID: 0, - // True, if the document source is modifiable. e.g. HTML and not PDF. - previewModifiable: true, - printToPDF: true, - deviceName: 'Save as PDF', - generateDraftData: true, - dpiHorizontal: 72, - dpiVertical: 72, - rasterizePDF: false, - duplex: 0, - copies: 1, - // 2 = color - see ColorModel in //printing/print_job_constants.h - color: 2, - collate: true, - printerType: 2, - title: undefined as string | undefined, - url: undefined as string | undefined -} as const; - // JavaScript implementations of WebContents. const binding = process._linkedBinding('electron_browser_web_contents'); const printing = process._linkedBinding('electron_browser_printing'); @@ -193,136 +171,136 @@ WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (worldId let pendingPromise: Promise | undefined; WebContents.prototype.printToPDF = async function (options) { const printSettings: Record = { - ...defaultPrintingSetting, - requestID: getNextId() + requestID: getNextId(), + landscape: false, + displayHeaderFooter: false, + headerTemplate: '', + footerTemplate: '', + printBackground: false, + scale: 1, + paperWidth: 8.5, + paperHeight: 11, + marginTop: 0, + marginBottom: 0, + marginLeft: 0, + marginRight: 0, + pageRanges: '', + preferCSSPageSize: false }; if (options.landscape !== undefined) { if (typeof options.landscape !== 'boolean') { - const error = new Error('landscape must be a Boolean'); - return Promise.reject(error); + return Promise.reject(new Error('landscape must be a Boolean')); } printSettings.landscape = options.landscape; } - if (options.scaleFactor !== undefined) { - if (typeof options.scaleFactor !== 'number') { - const error = new Error('scaleFactor must be a Number'); - return Promise.reject(error); + if (options.displayHeaderFooter !== undefined) { + if (typeof options.displayHeaderFooter !== 'boolean') { + return Promise.reject(new Error('displayHeaderFooter must be a Boolean')); } - printSettings.scaleFactor = options.scaleFactor; + printSettings.displayHeaderFooter = options.displayHeaderFooter; } - if (options.marginsType !== undefined) { - if (typeof options.marginsType !== 'number') { - const error = new Error('marginsType must be a Number'); - return Promise.reject(error); + if (options.printBackground !== undefined) { + if (typeof options.printBackground !== 'boolean') { + return Promise.reject(new Error('printBackground must be a Boolean')); } - printSettings.marginsType = options.marginsType; + printSettings.shouldPrintBackgrounds = options.printBackground; } - if (options.printSelectionOnly !== undefined) { - if (typeof options.printSelectionOnly !== 'boolean') { - const error = new Error('printSelectionOnly must be a Boolean'); - return Promise.reject(error); + if (options.scale !== undefined) { + if (typeof options.scale !== 'number') { + return Promise.reject(new Error('scale must be a Number')); } - printSettings.shouldPrintSelectionOnly = options.printSelectionOnly; + printSettings.scaleFactor = options.scale; } - if (options.printBackground !== undefined) { - if (typeof options.printBackground !== 'boolean') { - const error = new Error('printBackground must be a Boolean'); - return Promise.reject(error); + const { pageSize } = options; + if (pageSize !== undefined) { + if (typeof pageSize === 'string') { + const format = paperFormats[pageSize.toLowerCase()]; + if (!format) { + return Promise.reject(new Error(`Invalid pageSize ${pageSize}`)); + } + + printSettings.paperWidth = format.width; + printSettings.paperHeight = format.height; + } else if (typeof options.pageSize === 'object') { + if (!pageSize.height || !pageSize.width) { + return Promise.reject(new Error('height and width properties are required for pageSize')); + } + + printSettings.paperWidth = pageSize.width; + printSettings.paperHeight = pageSize.height; + } else { + return Promise.reject(new Error('pageSize must be a String or Object')); } - printSettings.shouldPrintBackgrounds = options.printBackground; } - if (options.pageRanges !== undefined) { - const pageRanges = options.pageRanges; - if (!Object.prototype.hasOwnProperty.call(pageRanges, 'from') || !Object.prototype.hasOwnProperty.call(pageRanges, 'to')) { - const error = new Error('pageRanges must be an Object with \'from\' and \'to\' properties'); - return Promise.reject(error); + const { margins } = options; + if (margins !== undefined) { + if (typeof margins !== 'object') { + return Promise.reject(new Error('margins must be an Object')); } - if (typeof pageRanges.from !== 'number') { - const error = new Error('pageRanges.from must be a Number'); - return Promise.reject(error); + if (margins.top !== undefined) { + if (typeof margins.top !== 'number') { + return Promise.reject(new Error('margins.top must be a Number')); + } + printSettings.marginTop = margins.top; } - if (typeof pageRanges.to !== 'number') { - const error = new Error('pageRanges.to must be a Number'); - return Promise.reject(error); + if (margins.bottom !== undefined) { + if (typeof margins.bottom !== 'number') { + return Promise.reject(new Error('margins.bottom must be a Number')); + } + printSettings.marginBottom = margins.bottom; } - // Chromium uses 1-based page ranges, so increment each by 1. - printSettings.pageRange = [{ - from: pageRanges.from + 1, - to: pageRanges.to + 1 - }]; - } - - if (options.headerFooter !== undefined) { - const headerFooter = options.headerFooter; - printSettings.headerFooterEnabled = true; - if (typeof headerFooter === 'object') { - if (!headerFooter.url || !headerFooter.title) { - const error = new Error('url and title properties are required for headerFooter'); - return Promise.reject(error); - } - if (typeof headerFooter.title !== 'string') { - const error = new Error('headerFooter.title must be a String'); - return Promise.reject(error); + if (margins.left !== undefined) { + if (typeof margins.left !== 'number') { + return Promise.reject(new Error('margins.left must be a Number')); } - printSettings.title = headerFooter.title; + printSettings.marginLeft = margins.left; + } - if (typeof headerFooter.url !== 'string') { - const error = new Error('headerFooter.url must be a String'); - return Promise.reject(error); + if (margins.right !== undefined) { + if (typeof margins.right !== 'number') { + return Promise.reject(new Error('margins.right must be a Number')); } - printSettings.url = headerFooter.url; - } else { - const error = new Error('headerFooter must be an Object'); - return Promise.reject(error); + printSettings.marginRight = margins.right; } } - // Optionally set size for PDF. - if (options.pageSize !== undefined) { - const pageSize = options.pageSize; - if (typeof pageSize === 'object') { - if (!pageSize.height || !pageSize.width) { - const error = new Error('height and width properties are required for pageSize'); - return Promise.reject(error); - } + if (options.pageRanges !== undefined) { + if (typeof options.pageRanges !== 'string') { + return Promise.reject(new Error('printBackground must be a String')); + } + printSettings.pageRanges = options.pageRanges; + } - // Dimensions in Microns - 1 meter = 10^6 microns - const height = Math.ceil(pageSize.height); - const width = Math.ceil(pageSize.width); - if (!isValidCustomPageSize(width, height)) { - const error = new Error('height and width properties must be minimum 352 microns.'); - return Promise.reject(error); - } + if (options.headerTemplate !== undefined) { + if (typeof options.headerTemplate !== 'string') { + return Promise.reject(new Error('headerTemplate must be a String')); + } + printSettings.headerTemplate = options.headerTemplate; + } - printSettings.mediaSize = { - name: 'CUSTOM', - custom_display_name: 'Custom', - height_microns: height, - width_microns: width - }; - } else if (Object.prototype.hasOwnProperty.call(PDFPageSizes, pageSize)) { - printSettings.mediaSize = PDFPageSizes[pageSize]; - } else { - const error = new Error(`Unsupported pageSize: ${pageSize}`); - return Promise.reject(error); + if (options.footerTemplate !== undefined) { + if (typeof options.footerTemplate !== 'string') { + return Promise.reject(new Error('footerTemplate must be a String')); } - } else { - printSettings.mediaSize = PDFPageSizes.A4; + printSettings.footerTemplate = options.footerTemplate; + } + + if (options.preferCSSPageSize !== undefined) { + if (typeof options.preferCSSPageSize !== 'boolean') { + return Promise.reject(new Error('footerTemplate must be a String')); + } + printSettings.preferCSSPageSize = options.preferCSSPageSize; } - // Chromium expects this in a 0-100 range number, not as float - printSettings.scaleFactor = Math.ceil(printSettings.scaleFactor) % 100; - // PrinterType enum from //printing/print_job_constants.h - printSettings.printerType = 2; if (this._printToPDF) { if (pendingPromise) { pendingPromise = pendingPromise.then(() => this._printToPDF(printSettings)); diff --git a/patches/chromium/printing.patch b/patches/chromium/printing.patch index 2b9df64a7bf5e..edeed181bef81 100644 --- a/patches/chromium/printing.patch +++ b/patches/chromium/printing.patch @@ -113,31 +113,19 @@ index dd27bbf387718d6abda5080e7d2c609cd0eaff17..8837cf2aeaa2f87d51be8d00aa356c8a void PrintJobWorkerOop::UnregisterServiceManagerClient() { diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc -index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc9c122ae3 100644 +index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..203ca9b0c4bf048016023fc3d260df6588392418 100644 --- a/chrome/browser/printing/print_view_manager_base.cc +++ b/chrome/browser/printing/print_view_manager_base.cc -@@ -30,10 +30,10 @@ +@@ -30,8 +30,6 @@ #include "chrome/browser/printing/print_view_manager_common.h" #include "chrome/browser/printing/printer_query.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ui/simple_message_box.h" -#include "chrome/browser/ui/webui/print_preview/printer_handler.h" #include "chrome/common/pref_names.h" -+#if 0 #include "chrome/grit/generated_resources.h" -+#endif #include "components/prefs/pref_service.h" - #include "components/printing/browser/print_composite_client.h" - #include "components/printing/browser/print_manager_utils.h" -@@ -48,6 +48,7 @@ - #include "content/public/browser/render_frame_host.h" - #include "content/public/browser/render_process_host.h" - #include "content/public/browser/web_contents.h" -+#include "chrome/grit/generated_resources.h" - #include "mojo/public/cpp/system/buffer.h" - #include "printing/buildflags/buildflags.h" - #include "printing/metafile_skia.h" -@@ -87,6 +88,8 @@ using PrintSettingsCallback = +@@ -87,6 +85,8 @@ using PrintSettingsCallback = base::OnceCallback)>; void ShowWarningMessageBox(const std::u16string& message) { @@ -146,7 +134,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc // Runs always on the UI thread. static bool is_dialog_shown = false; if (is_dialog_shown) -@@ -95,6 +98,7 @@ void ShowWarningMessageBox(const std::u16string& message) { +@@ -95,6 +95,7 @@ void ShowWarningMessageBox(const std::u16string& message) { base::AutoReset auto_reset(&is_dialog_shown, true); chrome::ShowWarningMessageBox(nullptr, std::u16string(), message); @@ -154,7 +142,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc } #if BUILDFLAG(ENABLE_PRINT_PREVIEW) -@@ -192,7 +196,9 @@ void UpdatePrintSettingsReplyOnIO( +@@ -192,7 +193,9 @@ void UpdatePrintSettingsReplyOnIO( DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK(printer_query); mojom::PrintPagesParamsPtr params = CreateEmptyPrintPagesParamsPtr(); @@ -165,7 +153,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc RenderParamsFromPrintSettings(printer_query->settings(), params->params.get()); params->params->document_cookie = printer_query->cookie(); -@@ -245,6 +251,7 @@ void ScriptedPrintReplyOnIO( +@@ -245,6 +248,7 @@ void ScriptedPrintReplyOnIO( mojom::PrintManagerHost::ScriptedPrintCallback callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); mojom::PrintPagesParamsPtr params = CreateEmptyPrintPagesParamsPtr(); @@ -173,7 +161,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc if (printer_query->last_status() == mojom::ResultCode::kSuccess && printer_query->settings().dpi()) { RenderParamsFromPrintSettings(printer_query->settings(), -@@ -254,8 +261,9 @@ void ScriptedPrintReplyOnIO( +@@ -254,8 +258,9 @@ void ScriptedPrintReplyOnIO( } bool has_valid_cookie = params->params->document_cookie; bool has_dpi = !params->params->dpi.IsEmpty(); @@ -184,7 +172,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc if (has_dpi && has_valid_cookie) { queue->QueuePrinterQuery(std::move(printer_query)); -@@ -293,12 +301,14 @@ PrintViewManagerBase::PrintViewManagerBase(content::WebContents* web_contents) +@@ -293,12 +298,14 @@ PrintViewManagerBase::PrintViewManagerBase(content::WebContents* web_contents) : PrintManager(web_contents), queue_(g_browser_process->print_job_manager()->queue()) { DCHECK(queue_); @@ -199,7 +187,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc } PrintViewManagerBase::~PrintViewManagerBase() { -@@ -306,7 +316,10 @@ PrintViewManagerBase::~PrintViewManagerBase() { +@@ -306,7 +313,10 @@ PrintViewManagerBase::~PrintViewManagerBase() { DisconnectFromCurrentPrintJob(); } @@ -211,7 +199,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc // Remember the ID for `rfh`, to enable checking that the `RenderFrameHost` // is still valid after a possible inner message loop runs in // `DisconnectFromCurrentPrintJob()`. -@@ -332,6 +345,9 @@ bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) { +@@ -332,6 +342,9 @@ bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) { #endif SetPrintingRFH(rfh); @@ -221,7 +209,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc #if BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS) enterprise_connectors::ContentAnalysisDelegate::Data scanning_data; -@@ -500,7 +516,8 @@ void PrintViewManagerBase::GetDefaultPrintSettingsReply( +@@ -500,7 +513,8 @@ void PrintViewManagerBase::GetDefaultPrintSettingsReply( void PrintViewManagerBase::ScriptedPrintReply( ScriptedPrintCallback callback, int process_id, @@ -231,7 +219,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc DCHECK_CURRENTLY_ON(content::BrowserThread::UI); #if BUILDFLAG(ENABLE_OOP_PRINTING) -@@ -513,16 +530,19 @@ void PrintViewManagerBase::ScriptedPrintReply( +@@ -513,16 +527,19 @@ void PrintViewManagerBase::ScriptedPrintReply( return; } @@ -255,7 +243,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc } void PrintViewManagerBase::NavigationStopped() { -@@ -638,11 +658,14 @@ void PrintViewManagerBase::DidPrintDocument( +@@ -638,11 +655,14 @@ void PrintViewManagerBase::DidPrintDocument( void PrintViewManagerBase::GetDefaultPrintSettings( GetDefaultPrintSettingsCallback callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -270,7 +258,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc #if BUILDFLAG(ENABLE_OOP_PRINTING) if (printing::features::kEnableOopPrintDriversJobPrint.Get() && !service_manager_client_id_.has_value()) { -@@ -672,18 +695,20 @@ void PrintViewManagerBase::UpdatePrintSettings( +@@ -672,18 +692,20 @@ void PrintViewManagerBase::UpdatePrintSettings( base::Value::Dict job_settings, UpdatePrintSettingsCallback callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -292,7 +280,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc content::BrowserContext* context = web_contents() ? web_contents()->GetBrowserContext() : nullptr; PrefService* prefs = -@@ -693,6 +718,7 @@ void PrintViewManagerBase::UpdatePrintSettings( +@@ -693,6 +715,7 @@ void PrintViewManagerBase::UpdatePrintSettings( if (value > 0) job_settings.Set(kSettingRasterizePdfDpi, value); } @@ -300,7 +288,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc auto callback_wrapper = base::BindOnce(&PrintViewManagerBase::UpdatePrintSettingsReply, -@@ -718,14 +744,14 @@ void PrintViewManagerBase::ScriptedPrint(mojom::ScriptedPrintParamsPtr params, +@@ -718,14 +741,14 @@ void PrintViewManagerBase::ScriptedPrint(mojom::ScriptedPrintParamsPtr params, // didn't happen for some reason. bad_message::ReceivedBadMessage( render_process_host, bad_message::PVMB_SCRIPTED_PRINT_FENCED_FRAME); @@ -317,7 +305,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc return; } #endif -@@ -763,7 +789,6 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie, +@@ -763,7 +786,6 @@ void PrintViewManagerBase::PrintingFailed(int32_t cookie, PrintManager::PrintingFailed(cookie, reason); #if !BUILDFLAG(IS_ANDROID) // Android does not implement this function. @@ -325,7 +313,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc #endif ReleasePrinterQuery(); -@@ -778,6 +803,11 @@ void PrintViewManagerBase::RemoveObserver(Observer& observer) { +@@ -778,6 +800,11 @@ void PrintViewManagerBase::RemoveObserver(Observer& observer) { } void PrintViewManagerBase::ShowInvalidPrinterSettingsError() { @@ -337,7 +325,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(&ShowWarningMessageBox, l10n_util::GetStringUTF16( -@@ -788,10 +818,12 @@ void PrintViewManagerBase::RenderFrameHostStateChanged( +@@ -788,10 +815,12 @@ void PrintViewManagerBase::RenderFrameHostStateChanged( content::RenderFrameHost* render_frame_host, content::RenderFrameHost::LifecycleState /*old_state*/, content::RenderFrameHost::LifecycleState new_state) { @@ -350,7 +338,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc } void PrintViewManagerBase::DidStartLoading() { -@@ -851,6 +883,11 @@ void PrintViewManagerBase::OnJobDone() { +@@ -851,6 +880,11 @@ void PrintViewManagerBase::OnJobDone() { ReleasePrintJob(); } @@ -362,7 +350,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc void PrintViewManagerBase::OnFailed() { TerminatePrintJob(true); } -@@ -908,7 +945,10 @@ bool PrintViewManagerBase::CreateNewPrintJob( +@@ -908,7 +942,10 @@ bool PrintViewManagerBase::CreateNewPrintJob( // Disconnect the current |print_job_|. auto weak_this = weak_ptr_factory_.GetWeakPtr(); @@ -374,7 +362,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc if (!weak_this) return false; -@@ -987,6 +1027,13 @@ void PrintViewManagerBase::ReleasePrintJob() { +@@ -987,6 +1024,13 @@ void PrintViewManagerBase::ReleasePrintJob() { UnregisterSystemPrintClient(); #endif @@ -388,7 +376,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc if (!print_job_) return; -@@ -1036,7 +1083,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() { +@@ -1036,7 +1080,7 @@ bool PrintViewManagerBase::RunInnerMessageLoop() { } bool PrintViewManagerBase::OpportunisticallyCreatePrintJob(int cookie) { @@ -397,7 +385,7 @@ index 3701853ada7f0ffe3cc8a798496f9f48541b4f47..973666a1315c8cbba0a2cefbe9195fdc return true; if (!cookie) { -@@ -1144,7 +1191,7 @@ void PrintViewManagerBase::SendPrintingEnabled(bool enabled, +@@ -1144,7 +1188,7 @@ void PrintViewManagerBase::SendPrintingEnabled(bool enabled, } void PrintViewManagerBase::CompletePrintNow(content::RenderFrameHost* rfh) { @@ -491,29 +479,6 @@ index 5f4d6e314b21351e3e5912e3a43ef87774343085..8627c8305686654dca7cd9c26433592e void PrintWithParams(mojom::PrintPagesParamsPtr params) override; void PrintForSystemDialog() override; void SetPrintPreviewUI( -diff --git a/components/printing/browser/print_to_pdf/pdf_print_manager.cc b/components/printing/browser/print_to_pdf/pdf_print_manager.cc -index 82591f8c2abbc1a180ef62f7264a68ca279e9b9c..ad27a15ba3028af1046482192dec789df5dda7b2 100644 ---- a/components/printing/browser/print_to_pdf/pdf_print_manager.cc -+++ b/components/printing/browser/print_to_pdf/pdf_print_manager.cc -@@ -132,7 +132,8 @@ void PdfPrintManager::PrintToPdf( - set_cookie(print_pages_params->params->document_cookie); - callback_ = std::move(callback); - -- GetPrintRenderFrame(rfh)->PrintWithParams(std::move(print_pages_params)); -+ // TODO(electron-maintainers): do something with job_settings here? -+ GetPrintRenderFrame(rfh)->PrintRequestedPages(true/*silent*/, base::Value{}/*job_settings*/); - } - - void PdfPrintManager::GetDefaultPrintSettings( -@@ -147,7 +148,7 @@ void PdfPrintManager::ScriptedPrint( - auto default_param = printing::mojom::PrintPagesParams::New(); - default_param->params = printing::mojom::PrintParams::New(); - DLOG(ERROR) << "Scripted print is not supported"; -- std::move(callback).Run(std::move(default_param)); -+ std::move(callback).Run(std::move(default_param), true/*canceled*/); - } - - void PdfPrintManager::ShowInvalidPrinterSettingsError() { diff --git a/components/printing/common/print.mojom b/components/printing/common/print.mojom index 8e5c441b3d0a2d35fc5c6f9d43b4a4ca167e09ca..2cfcd810c9507c434e673064b63e8fbc95172537 100644 --- a/components/printing/common/print.mojom @@ -537,7 +502,7 @@ index 8e5c441b3d0a2d35fc5c6f9d43b4a4ca167e09ca..2cfcd810c9507c434e673064b63e8fbc // Tells the browser that there are invalid printer settings. ShowInvalidPrinterSettingsError(); diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc -index db8913ae41d46d14fd15c6127e126e4b129dc4b8..ddbc3b0d5a00af9de84e1b0aadc44adb6ff84495 100644 +index db8913ae41d46d14fd15c6127e126e4b129dc4b8..eaddc1bbc59bad9cc885fb8532d4f8c1df2f1a86 100644 --- a/components/printing/renderer/print_render_frame_helper.cc +++ b/components/printing/renderer/print_render_frame_helper.cc @@ -42,6 +42,7 @@ @@ -716,26 +681,6 @@ index db8913ae41d46d14fd15c6127e126e4b129dc4b8..ddbc3b0d5a00af9de84e1b0aadc44adb *output = std::move(input); std::move(quit_closure).Run(); }, -@@ -2725,18 +2756,7 @@ void PrintRenderFrameHelper::RequestPrintPreview(PrintPreviewRequestType type, - } - - bool PrintRenderFrameHelper::CheckForCancel() { -- const mojom::PrintParams& print_params = *print_pages_params_->params; -- bool cancel = false; -- -- if (!GetPrintManagerHost()->CheckForCancel(print_params.preview_ui_id, -- print_params.preview_request_id, -- &cancel)) { -- cancel = true; -- } -- -- if (cancel) -- notify_browser_of_print_failure_ = false; -- return cancel; -+ return false; - } - - bool PrintRenderFrameHelper::PreviewPageRendered( diff --git a/components/printing/renderer/print_render_frame_helper.h b/components/printing/renderer/print_render_frame_helper.h index 220b28a7e63625fe8b76290f0d2f40dd32cae255..cff9e35fab9df680c3c39467c50ddb033c2e6cba 100644 --- a/components/printing/renderer/print_render_frame_helper.h diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index b48a45bb8bcc2..1422083e5d511 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -25,6 +25,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "base/values.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/printing/print_view_manager_base.h" #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" #include "chrome/browser/ui/views/eye_dropper/eye_dropper.h" #include "chrome/common/pref_names.h" @@ -165,9 +166,10 @@ #if BUILDFLAG(ENABLE_PRINTING) #include "components/printing/browser/print_manager_utils.h" +#include "components/printing/browser/print_to_pdf/pdf_print_utils.h" #include "printing/backend/print_backend.h" // nogncheck #include "printing/mojom/print.mojom.h" // nogncheck -#include "shell/browser/printing/print_preview_message_handler.h" +#include "printing/page_range.h" #include "shell/browser/printing/print_view_manager_electron.h" #if BUILDFLAG(IS_WIN) @@ -919,10 +921,7 @@ void WebContents::InitWithWebContents( web_contents->SetDelegate(this); #if BUILDFLAG(ENABLE_PRINTING) - PrintPreviewMessageHandler::CreateForWebContents(web_contents.get()); PrintViewManagerElectron::CreateForWebContents(web_contents.get()); - printing::CreateCompositeClientIfNeeded(web_contents.get(), - browser_context->GetUserAgent()); #endif #if BUILDFLAG(ENABLE_PDF_VIEWER) @@ -2807,14 +2806,87 @@ void WebContents::Print(gin::Arguments* args) { std::move(callback), device_name, silent)); } -v8::Local WebContents::PrintToPDF(base::DictionaryValue settings) { +// Partially duplicated and modified from +// headless/lib/browser/protocol/page_handler.cc;l=41 +v8::Local WebContents::PrintToPDF(const base::Value& settings) { v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); gin_helper::Promise> promise(isolate); v8::Local handle = promise.GetHandle(); - PrintPreviewMessageHandler::FromWebContents(web_contents()) - ->PrintToPDF(std::move(settings), std::move(promise)); + + // This allows us to track headless printing calls. + auto unique_id = settings.GetDict().FindInt(printing::kPreviewRequestID); + auto landscape = settings.GetDict().FindBool("landscape"); + auto display_header_footer = + settings.GetDict().FindBool("displayHeaderFooter"); + auto print_background = settings.GetDict().FindBool("shouldPrintBackgrounds"); + auto scale = settings.GetDict().FindDouble("scale"); + auto paper_width = settings.GetDict().FindInt("paperWidth"); + auto paper_height = settings.GetDict().FindInt("paperHeight"); + auto margin_top = settings.GetDict().FindIntByDottedPath("margins.top"); + auto margin_bottom = settings.GetDict().FindIntByDottedPath("margins.bottom"); + auto margin_left = settings.GetDict().FindIntByDottedPath("margins.left"); + auto margin_right = settings.GetDict().FindIntByDottedPath("margins.right"); + 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"); + + absl::variant + print_pages_params = print_to_pdf::GetPrintPagesParams( + web_contents()->GetMainFrame()->GetLastCommittedURL(), landscape, + display_header_footer, print_background, scale, paper_width, + paper_height, margin_top, margin_bottom, margin_left, margin_right, + absl::make_optional(header_template), + absl::make_optional(footer_template), prefer_css_page_size); + + if (absl::holds_alternative(print_pages_params)) { + auto error = absl::get(print_pages_params); + promise.RejectWithErrorMessage("Invalid print parameters: " + error); + return handle; + } + + auto* manager = PrintViewManagerElectron::FromWebContents(web_contents()); + if (!manager) { + promise.RejectWithErrorMessage("Failed to find print manager"); + return handle; + } + + auto params = std::move( + absl::get(print_pages_params)); + params->params->document_cookie = unique_id.value_or(0); + + manager->PrintToPdf(web_contents()->GetMainFrame(), page_ranges, + std::move(params), + base::BindOnce(&WebContents::OnPDFCreated, GetWeakPtr(), + std::move(promise))); + return handle; } + +void WebContents::OnPDFCreated( + gin_helper::Promise> promise, + PrintViewManagerElectron::PrintResult print_result, + scoped_refptr data) { + if (print_result != PrintViewManagerElectron::PrintResult::PRINT_SUCCESS) { + promise.RejectWithErrorMessage( + "Failed to generate PDF: " + + PrintViewManagerElectron::PrintResultToString(print_result)); + return; + } + + v8::Isolate* isolate = promise.isolate(); + gin_helper::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope( + v8::Local::New(isolate, promise.GetContext())); + + v8::Local buffer = + node::Buffer::Copy(isolate, reinterpret_cast(data->front()), + data->size()) + .ToLocalChecked(); + + promise.Resolve(buffer); +} #endif void WebContents::AddWorkSpace(gin::Arguments* args, diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index 8925e14db4f5c..c85708f45cd16 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -47,7 +47,6 @@ #include "ui/gfx/image/image.h" #if BUILDFLAG(ENABLE_PRINTING) -#include "shell/browser/printing/print_preview_message_handler.h" #include "shell/browser/printing/print_view_manager_electron.h" #endif @@ -231,7 +230,10 @@ class WebContents : public ExclusiveAccessContext, std::pair info); void Print(gin::Arguments* args); // Print current page as PDF. - v8::Local PrintToPDF(base::DictionaryValue settings); + v8::Local PrintToPDF(const base::Value& settings); + void OnPDFCreated(gin_helper::Promise> promise, + PrintViewManagerElectron::PrintResult print_result, + scoped_refptr data); #endif void SetNextChildWebPreferences(const gin_helper::Dictionary); diff --git a/shell/browser/extensions/electron_extensions_api_client.cc b/shell/browser/extensions/electron_extensions_api_client.cc index b8ac568e10722..1a4c24700b9fc 100644 --- a/shell/browser/extensions/electron_extensions_api_client.cc +++ b/shell/browser/extensions/electron_extensions_api_client.cc @@ -19,7 +19,6 @@ #if BUILDFLAG(ENABLE_PRINTING) #include "components/printing/browser/print_manager_utils.h" -#include "shell/browser/printing/print_preview_message_handler.h" #include "shell/browser/printing/print_view_manager_electron.h" #endif @@ -86,7 +85,6 @@ MessagingDelegate* ElectronExtensionsAPIClient::GetMessagingDelegate() { void ElectronExtensionsAPIClient::AttachWebContentsHelpers( content::WebContents* web_contents) const { #if BUILDFLAG(ENABLE_PRINTING) - electron::PrintPreviewMessageHandler::CreateForWebContents(web_contents); electron::PrintViewManagerElectron::CreateForWebContents(web_contents); #endif diff --git a/shell/browser/printing/print_preview_message_handler.h b/shell/browser/printing/print_preview_message_handler.h deleted file mode 100644 index 57d84767d7759..0000000000000 --- a/shell/browser/printing/print_preview_message_handler.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2018 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ELECTRON_SHELL_BROWSER_PRINTING_PRINT_PREVIEW_MESSAGE_HANDLER_H_ -#define ELECTRON_SHELL_BROWSER_PRINTING_PRINT_PREVIEW_MESSAGE_HANDLER_H_ - -#include - -#include "base/memory/ref_counted_memory.h" -#include "base/memory/weak_ptr.h" -#include "components/printing/common/print.mojom.h" -#include "components/services/print_compositor/public/mojom/print_compositor.mojom.h" -#include "content/public/browser/web_contents_user_data.h" -#include "mojo/public/cpp/bindings/associated_receiver.h" -#include "mojo/public/cpp/bindings/associated_remote.h" -#include "printing/mojom/print.mojom.h" -#include "shell/common/gin_helper/promise.h" -#include "v8/include/v8.h" - -namespace content { -class RenderFrameHost; -} - -namespace electron { - -// Manages the print preview handling for a WebContents. -class PrintPreviewMessageHandler - : public printing::mojom::PrintPreviewUI, - public content::WebContentsUserData { - public: - ~PrintPreviewMessageHandler() override; - - // disable copy - PrintPreviewMessageHandler(const PrintPreviewMessageHandler&) = delete; - PrintPreviewMessageHandler& operator=(const PrintPreviewMessageHandler&) = - delete; - - void PrintToPDF(base::DictionaryValue options, - gin_helper::Promise> promise); - - private: - friend class content::WebContentsUserData; - - explicit PrintPreviewMessageHandler(content::WebContents* web_contents); - - void OnCompositeDocumentToPdfDone( - int32_t request_id, - printing::mojom::PrintCompositor::Status status, - base::ReadOnlySharedMemoryRegion region); - void OnPrepareForDocumentToPdfDone( - int32_t request_id, - printing::mojom::PrintCompositor::Status status); - void OnCompositePdfPageDone(int page_number, - int document_cookie, - int32_t request_id, - printing::mojom::PrintCompositor::Status status, - base::ReadOnlySharedMemoryRegion region); - - // printing::mojo::PrintPreviewUI: - void SetOptionsFromDocument( - const printing::mojom::OptionsFromDocumentParamsPtr params, - int32_t request_id) override {} - void PrintPreviewFailed(int32_t document_cookie, int32_t request_id) override; - void PrintPreviewCancelled(int32_t document_cookie, - int32_t request_id) override; - void PrinterSettingsInvalid(int32_t document_cookie, - int32_t request_id) override {} - void DidPrepareDocumentForPreview(int32_t document_cookie, - int32_t request_id) override; - void DidPreviewPage(printing::mojom::DidPreviewPageParamsPtr params, - int32_t request_id) override; - void MetafileReadyForPrinting( - printing::mojom::DidPreviewDocumentParamsPtr params, - int32_t request_id) override; - void DidGetDefaultPageLayout( - printing::mojom::PageSizeMarginsPtr page_layout_in_points, - const gfx::Rect& printable_area_in_points, - bool has_custom_page_size_style, - int32_t request_id) override {} - void DidStartPreview(printing::mojom::DidStartPreviewParamsPtr params, - int32_t request_id) override {} - - gin_helper::Promise> GetPromise(int request_id); - - void ResolvePromise(int request_id, - scoped_refptr data_bytes); - void RejectPromise(int request_id); - - using PromiseMap = std::map>>; - PromiseMap promise_map_; - - // TODO(clavin): refactor to use the WebContents provided by the - // WebContentsUserData base class instead of storing a duplicate ref - content::WebContents* web_contents_ = nullptr; - - mojo::AssociatedRemote print_render_frame_; - - mojo::AssociatedReceiver receiver_{this}; - - base::WeakPtrFactory weak_ptr_factory_{this}; - - WEB_CONTENTS_USER_DATA_KEY_DECL(); -}; - -} // namespace electron - -#endif // ELECTRON_SHELL_BROWSER_PRINTING_PRINT_PREVIEW_MESSAGE_HANDLER_H_ diff --git a/shell/browser/printing/print_view_manager_electron.cc b/shell/browser/printing/print_view_manager_electron.cc index 505073093a7ca..866f439f5aec4 100644 --- a/shell/browser/printing/print_view_manager_electron.cc +++ b/shell/browser/printing/print_view_manager_electron.cc @@ -7,13 +7,39 @@ #include #include "build/build_config.h" -#include "content/public/browser/web_contents_user_data.h" +#include "components/printing/browser/print_to_pdf/pdf_print_utils.h" +#include "printing/mojom/print.mojom.h" +#include "printing/page_range.h" +#include "third_party/abseil-cpp/absl/types/variant.h" + +#if BUILDFLAG(ENABLE_PRINT_PREVIEW) +#include "mojo/public/cpp/bindings/message.h" +#endif namespace electron { +namespace { + +#if BUILDFLAG(ENABLE_PRINT_PREVIEW) +constexpr char kInvalidUpdatePrintSettingsCall[] = + "Invalid UpdatePrintSettings Call"; +constexpr char kInvalidSetupScriptedPrintPreviewCall[] = + "Invalid SetupScriptedPrintPreview Call"; +constexpr char kInvalidShowScriptedPrintPreviewCall[] = + "Invalid ShowScriptedPrintPreview Call"; +constexpr char kInvalidRequestPrintPreviewCall[] = + "Invalid RequestPrintPreview Call"; +#endif + +} // namespace + +// This file subclasses printing::PrintViewManagerBase +// but the implementations are duplicated from +// components/printing/browser/print_to_pdf/pdf_print_manager.cc. + PrintViewManagerElectron::PrintViewManagerElectron( content::WebContents* web_contents) - : PrintViewManagerBase(web_contents), + : printing::PrintViewManagerBase(web_contents), content::WebContentsUserData(*web_contents) {} PrintViewManagerElectron::~PrintViewManagerElectron() = default; @@ -25,26 +51,237 @@ void PrintViewManagerElectron::BindPrintManagerHost( auto* web_contents = content::WebContents::FromRenderFrameHost(rfh); if (!web_contents) return; + auto* print_manager = PrintViewManagerElectron::FromWebContents(web_contents); if (!print_manager) return; + print_manager->BindReceiver(std::move(receiver), rfh); } +// static +std::string PrintViewManagerElectron::PrintResultToString(PrintResult result) { + switch (result) { + case PRINT_SUCCESS: + return std::string(); // no error message + case PRINTING_FAILED: + return "Printing failed"; + case INVALID_PRINTER_SETTINGS: + return "Show invalid printer settings error"; + case INVALID_MEMORY_HANDLE: + return "Invalid memory handle"; + case METAFILE_MAP_ERROR: + return "Map to shared memory error"; + case METAFILE_INVALID_HEADER: + return "Invalid metafile header"; + case METAFILE_GET_DATA_ERROR: + return "Get data from metafile error"; + case SIMULTANEOUS_PRINT_ACTIVE: + return "The previous printing job hasn't finished"; + case PAGE_RANGE_SYNTAX_ERROR: + return "Page range syntax error"; + case PAGE_RANGE_INVALID_RANGE: + return "Page range is invalid (start > end)"; + case PAGE_COUNT_EXCEEDED: + return "Page range exceeds page count"; + default: + NOTREACHED(); + return "Unknown PrintResult"; + } +} + +void PrintViewManagerElectron::PrintToPdf( + content::RenderFrameHost* rfh, + const std::string& page_ranges, + printing::mojom::PrintPagesParamsPtr print_pages_params, + PrintToPDFCallback callback) { + DCHECK(callback); + + if (callback_) { + std::move(callback).Run(SIMULTANEOUS_PRINT_ACTIVE, + base::MakeRefCounted()); + return; + } + + if (!rfh->IsRenderFrameLive()) { + std::move(callback).Run(PRINTING_FAILED, + base::MakeRefCounted()); + return; + } + + absl::variant + parsed_ranges = print_to_pdf::TextPageRangesToPageRanges(page_ranges); + if (absl::holds_alternative(parsed_ranges)) { + PrintResult print_result; + switch (absl::get(parsed_ranges)) { + case print_to_pdf::PageRangeError::kSyntaxError: + print_result = PAGE_RANGE_SYNTAX_ERROR; + break; + case print_to_pdf::PageRangeError::kInvalidRange: + print_result = PAGE_RANGE_INVALID_RANGE; + break; + } + std::move(callback).Run(print_result, + base::MakeRefCounted()); + return; + } + + printing_rfh_ = rfh; + print_pages_params->pages = absl::get(parsed_ranges); + auto cookie = print_pages_params->params->document_cookie; + set_cookie(cookie); + headless_jobs_.emplace_back(cookie); + callback_ = std::move(callback); + + GetPrintRenderFrame(rfh)->PrintWithParams(std::move(print_pages_params)); +} + +void PrintViewManagerElectron::GetDefaultPrintSettings( + GetDefaultPrintSettingsCallback callback) { + if (printing_rfh_) { + LOG(ERROR) << "Scripted print is not supported"; + std::move(callback).Run(printing::mojom::PrintParams::New()); + } else { + PrintViewManagerBase::GetDefaultPrintSettings(std::move(callback)); + } +} + +void PrintViewManagerElectron::ScriptedPrint( + printing::mojom::ScriptedPrintParamsPtr params, + ScriptedPrintCallback callback) { + auto entry = + std::find(headless_jobs_.begin(), headless_jobs_.end(), params->cookie); + if (entry == headless_jobs_.end()) { + PrintViewManagerBase::ScriptedPrint(std::move(params), std::move(callback)); + return; + } + + auto default_param = printing::mojom::PrintPagesParams::New(); + default_param->params = printing::mojom::PrintParams::New(); + LOG(ERROR) << "Scripted print is not supported"; + std::move(callback).Run(std::move(default_param), /*cancelled*/ false); +} + +void PrintViewManagerElectron::ShowInvalidPrinterSettingsError() { + ReleaseJob(INVALID_PRINTER_SETTINGS); +} + +void PrintViewManagerElectron::PrintingFailed( + int32_t cookie, + printing::mojom::PrintFailureReason reason) { + ReleaseJob(reason == printing::mojom::PrintFailureReason::kInvalidPageRange + ? PAGE_COUNT_EXCEEDED + : PRINTING_FAILED); +} + +#if BUILDFLAG(ENABLE_PRINT_PREVIEW) +void PrintViewManagerElectron::UpdatePrintSettings( + int32_t cookie, + base::Value::Dict job_settings, + UpdatePrintSettingsCallback callback) { + auto entry = std::find(headless_jobs_.begin(), headless_jobs_.end(), cookie); + if (entry == headless_jobs_.end()) { + PrintViewManagerBase::UpdatePrintSettings(cookie, std::move(job_settings), + std::move(callback)); + return; + } + + mojo::ReportBadMessage(kInvalidUpdatePrintSettingsCall); +} + void PrintViewManagerElectron::SetupScriptedPrintPreview( SetupScriptedPrintPreviewCallback callback) { - std::move(callback).Run(); + mojo::ReportBadMessage(kInvalidSetupScriptedPrintPreviewCall); } void PrintViewManagerElectron::ShowScriptedPrintPreview( - bool source_is_modifiable) {} + bool source_is_modifiable) { + mojo::ReportBadMessage(kInvalidShowScriptedPrintPreviewCall); +} void PrintViewManagerElectron::RequestPrintPreview( - printing::mojom::RequestPrintPreviewParamsPtr params) {} + printing::mojom::RequestPrintPreviewParamsPtr params) { + mojo::ReportBadMessage(kInvalidRequestPrintPreviewCall); +} void PrintViewManagerElectron::CheckForCancel(int32_t preview_ui_id, int32_t request_id, CheckForCancelCallback callback) { + std::move(callback).Run(false); +} +#endif // BUILDFLAG(ENABLE_PRINT_PREVIEW) + +void PrintViewManagerElectron::RenderFrameDeleted( + content::RenderFrameHost* render_frame_host) { + PrintViewManagerBase::RenderFrameDeleted(render_frame_host); + + if (printing_rfh_ != render_frame_host) + return; + + if (callback_) { + std::move(callback_).Run(PRINTING_FAILED, + base::MakeRefCounted()); + } + + Reset(); +} + +void PrintViewManagerElectron::DidGetPrintedPagesCount(int32_t cookie, + uint32_t number_pages) { + auto entry = std::find(headless_jobs_.begin(), headless_jobs_.end(), cookie); + if (entry == headless_jobs_.end()) { + PrintViewManagerBase::DidGetPrintedPagesCount(cookie, number_pages); + } +} + +void PrintViewManagerElectron::DidPrintDocument( + printing::mojom::DidPrintDocumentParamsPtr params, + DidPrintDocumentCallback callback) { + auto entry = std::find(headless_jobs_.begin(), headless_jobs_.end(), + params->document_cookie); + if (entry == headless_jobs_.end()) { + PrintViewManagerBase::DidPrintDocument(std::move(params), + std::move(callback)); + return; + } + + auto& content = *params->content; + if (!content.metafile_data_region.IsValid()) { + ReleaseJob(INVALID_MEMORY_HANDLE); + std::move(callback).Run(false); + return; + } + + base::ReadOnlySharedMemoryMapping map = content.metafile_data_region.Map(); + if (!map.IsValid()) { + ReleaseJob(METAFILE_MAP_ERROR); + std::move(callback).Run(false); + return; + } + + data_ = std::string(static_cast(map.memory()), map.size()); + headless_jobs_.erase(entry); + std::move(callback).Run(true); + ReleaseJob(PRINT_SUCCESS); +} + +void PrintViewManagerElectron::Reset() { + printing_rfh_ = nullptr; + callback_.Reset(); + data_.clear(); +} + +void PrintViewManagerElectron::ReleaseJob(PrintResult result) { + if (callback_) { + DCHECK(result == PRINT_SUCCESS || data_.empty()); + std::move(callback_).Run(result, + base::RefCountedString::TakeString(&data_)); + if (printing_rfh_ && printing_rfh_->IsRenderFrameLive()) { + GetPrintRenderFrame(printing_rfh_)->PrintingDone(result == PRINT_SUCCESS); + } + + Reset(); + } } WEB_CONTENTS_USER_DATA_KEY_IMPL(PrintViewManagerElectron); diff --git a/shell/browser/printing/print_view_manager_electron.h b/shell/browser/printing/print_view_manager_electron.h index 2127219f953ce..197b3c77adfa4 100644 --- a/shell/browser/printing/print_view_manager_electron.h +++ b/shell/browser/printing/print_view_manager_electron.h @@ -5,9 +5,19 @@ #ifndef ELECTRON_SHELL_BROWSER_PRINTING_PRINT_VIEW_MANAGER_ELECTRON_H_ #define ELECTRON_SHELL_BROWSER_PRINTING_PRINT_VIEW_MANAGER_ELECTRON_H_ +#include +#include +#include + +#include "base/memory/raw_ptr.h" +#include "base/memory/ref_counted_memory.h" #include "build/build_config.h" #include "chrome/browser/printing/print_view_manager_base.h" +#include "components/printing/common/print.mojom.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" +#include "printing/print_settings.h" namespace electron { @@ -15,9 +25,26 @@ class PrintViewManagerElectron : public printing::PrintViewManagerBase, public content::WebContentsUserData { public: + enum PrintResult { + PRINT_SUCCESS, + PRINTING_FAILED, + INVALID_PRINTER_SETTINGS, + INVALID_MEMORY_HANDLE, + METAFILE_MAP_ERROR, + METAFILE_INVALID_HEADER, + METAFILE_GET_DATA_ERROR, + SIMULTANEOUS_PRINT_ACTIVE, + PAGE_RANGE_SYNTAX_ERROR, + PAGE_RANGE_INVALID_RANGE, + PAGE_COUNT_EXCEEDED, + }; + + using PrintToPDFCallback = + base::OnceCallback)>; + ~PrintViewManagerElectron() override; - // disable copy PrintViewManagerElectron(const PrintViewManagerElectron&) = delete; PrintViewManagerElectron& operator=(const PrintViewManagerElectron&) = delete; @@ -26,6 +53,35 @@ class PrintViewManagerElectron receiver, content::RenderFrameHost* rfh); + static std::string PrintResultToString(PrintResult result); + + void PrintToPdf(content::RenderFrameHost* rfh, + const std::string& page_ranges, + printing::mojom::PrintPagesParamsPtr print_page_params, + PrintToPDFCallback callback); + + private: + explicit PrintViewManagerElectron(content::WebContents* web_contents); + friend class content::WebContentsUserData; + + // WebContentsObserver overrides (via PrintManager): + void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; + + // printing::mojom::PrintManagerHost: + void DidPrintDocument(printing::mojom::DidPrintDocumentParamsPtr params, + DidPrintDocumentCallback callback) override; + void DidGetPrintedPagesCount(int32_t cookie, uint32_t number_pages) override; + void GetDefaultPrintSettings( + GetDefaultPrintSettingsCallback callback) override; + void ScriptedPrint(printing::mojom::ScriptedPrintParamsPtr params, + ScriptedPrintCallback callback) override; + void ShowInvalidPrinterSettingsError() override; + void PrintingFailed(int32_t cookie, + printing::mojom::PrintFailureReason reason) override; +#if BUILDFLAG(ENABLE_PRINT_PREVIEW) + void UpdatePrintSettings(int32_t cookie, + base::Value::Dict job_settings, + UpdatePrintSettingsCallback callback) override; void SetupScriptedPrintPreview( SetupScriptedPrintPreviewCallback callback) override; void ShowScriptedPrintPreview(bool source_is_modifiable) override; @@ -34,10 +90,15 @@ class PrintViewManagerElectron void CheckForCancel(int32_t preview_ui_id, int32_t request_id, CheckForCancelCallback callback) override; +#endif - private: - friend class content::WebContentsUserData; - explicit PrintViewManagerElectron(content::WebContents* web_contents); + void Reset(); + void ReleaseJob(PrintResult result); + + raw_ptr printing_rfh_ = nullptr; + PrintToPDFCallback callback_; + std::string data_; + std::vector headless_jobs_; WEB_CONTENTS_USER_DATA_KEY_DECL(); }; diff --git a/spec-main/api-web-contents-spec.ts b/spec-main/api-web-contents-spec.ts index 29ff9e5f60996..c340d309c83ac 100644 --- a/spec-main/api-web-contents-spec.ts +++ b/spec-main/api-web-contents-spec.ts @@ -1813,14 +1813,16 @@ describe('webContents module', () => { it('rejects on incorrectly typed parameters', async () => { const badTypes = { - marginsType: 'terrible', - scaleFactor: 'not-a-number', landscape: [], - pageRanges: { oops: 'im-not-the-right-key' }, - headerFooter: '123', - printSelectionOnly: 1, + displayHeaderFooter: '123', printBackground: 2, - pageSize: 'IAmAPageSize' + scale: 'not-a-number', + pageSize: 'IAmAPageSize', + margins: 'terrible', + pageRanges: { oops: 'im-not-the-right-key' }, + headerTemplate: [1, 2, 3], + footerTemplate: [4, 5, 6], + preferCSSPageSize: 'no' }; // These will hard crash in Chromium unless we type-check @@ -1869,10 +1871,7 @@ describe('webContents module', () => { it('respects custom settings', async () => { const data = await w.webContents.printToPDF({ - pageRanges: { - from: 0, - to: 2 - }, + pageRanges: '1-3', landscape: true }); diff --git a/spec/ts-smoke/electron/main.ts b/spec/ts-smoke/electron/main.ts index 6992f92e8b2f8..5d1b6dd6b66a3 100644 --- a/spec/ts-smoke/electron/main.ts +++ b/spec/ts-smoke/electron/main.ts @@ -86,10 +86,11 @@ app.whenReady().then(() => { mainWindow.webContents.print() mainWindow.webContents.printToPDF({ - marginsType: 1, - pageSize: 'A3', + margins: { + top: 1 + }, printBackground: true, - printSelectionOnly: true, + pageRanges: '1-3', landscape: true }).then((data: Buffer) => console.log(data)) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index f4af0158f2c85..377d96b396660 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1110,14 +1110,16 @@ describe(' tag', function () { ifdescribe(features.isPrintingEnabled())('.printToPDF()', () => { it('rejects on incorrectly typed parameters', async () => { const badTypes = { - marginsType: 'terrible', - scaleFactor: 'not-a-number', landscape: [], - pageRanges: { oops: 'im-not-the-right-key' }, - headerFooter: '123', - printSelectionOnly: 1, + displayHeaderFooter: '123', printBackground: 2, - pageSize: 'IAmAPageSize' + scale: 'not-a-number', + pageSize: 'IAmAPageSize', + margins: 'terrible', + pageRanges: { oops: 'im-not-the-right-key' }, + headerTemplate: [1, 2, 3], + footerTemplate: [4, 5, 6], + preferCSSPageSize: 'no' }; // These will hard crash in Chromium unless we type-check diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index cb8b2efc0e486..9ce22a848c2f8 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -273,6 +273,11 @@ declare namespace ElectronInternal { is_default?: 'true', } + type PageSize = { + width: number, + height: number, + } + type ModuleLoader = () => any; interface ModuleEntry {