Browse Source

refactor: printToPDF should be headless (#33654)

Shelley Vohr 2 years ago
parent
commit
93b39b92b5

+ 0 - 2
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",

+ 2 - 0
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) {

+ 20 - 33
docs/api/web-contents.md

@@ -1428,7 +1428,7 @@ Returns `Promise<PrinterInfo[]>` - 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<string, string> (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<string, number> (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, `<span class=title></span>` 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<Buffer>` - 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

+ 15 - 15
docs/api/webview-tag.md

@@ -565,21 +565,21 @@ Prints `webview`'s web page. Same as `webContents.print([options])`.
 ### `<webview>.printToPDF(options)`
 
 * `options` Object
-  * `headerFooter` Record<string, string> (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<string, number> (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, `<span class=title></span>` 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<Uint8Array>` - Resolves with the generated PDF data.
 

+ 55 - 0
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: '<h1>Title</h1>',
+  footerTemplate: '<div><span class="pageNumber"></span></div>',
+  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

+ 107 - 129
lib/browser/api/web-contents.ts

@@ -63,6 +63,20 @@ const PDFPageSizes: Record<string, ElectronInternal.MediaSize> = {
   }
 } as const;
 
+const paperFormats: Record<string, ElectronInternal.PageSize> = {
+  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<any> | undefined;
 WebContents.prototype.printToPDF = async function (options) {
   const printSettings: Record<string, any> = {
-    ...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));

+ 25 - 80
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(std::unique_ptr<PrinterQuery>)>;
  
  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<bool> 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

+ 79 - 7
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<v8::Promise> WebContents::PrintToPDF(base::DictionaryValue settings) {
+// Partially duplicated and modified from
+// headless/lib/browser/protocol/page_handler.cc;l=41
+v8::Local<v8::Promise> WebContents::PrintToPDF(const base::Value& settings) {
   v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
   gin_helper::Promise<v8::Local<v8::Value>> promise(isolate);
   v8::Local<v8::Promise> 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<printing::mojom::PrintPagesParamsPtr, std::string>
+      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<std::string>(print_pages_params)) {
+    auto error = absl::get<std::string>(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<printing::mojom::PrintPagesParamsPtr>(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<v8::Local<v8::Value>> promise,
+    PrintViewManagerElectron::PrintResult print_result,
+    scoped_refptr<base::RefCountedMemory> 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<v8::Context>::New(isolate, promise.GetContext()));
+
+  v8::Local<v8::Value> buffer =
+      node::Buffer::Copy(isolate, reinterpret_cast<const char*>(data->front()),
+                         data->size())
+          .ToLocalChecked();
+
+  promise.Resolve(buffer);
+}
 #endif
 
 void WebContents::AddWorkSpace(gin::Arguments* args,

+ 4 - 2
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<std::string, std::u16string> info);
   void Print(gin::Arguments* args);
   // Print current page as PDF.
-  v8::Local<v8::Promise> PrintToPDF(base::DictionaryValue settings);
+  v8::Local<v8::Promise> PrintToPDF(const base::Value& settings);
+  void OnPDFCreated(gin_helper::Promise<v8::Local<v8::Value>> promise,
+                    PrintViewManagerElectron::PrintResult print_result,
+                    scoped_refptr<base::RefCountedMemory> data);
 #endif
 
   void SetNextChildWebPreferences(const gin_helper::Dictionary);

+ 0 - 2
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
 

+ 0 - 108
shell/browser/printing/print_preview_message_handler.h

@@ -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 <map>
-
-#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<PrintPreviewMessageHandler> {
- public:
-  ~PrintPreviewMessageHandler() override;
-
-  // disable copy
-  PrintPreviewMessageHandler(const PrintPreviewMessageHandler&) = delete;
-  PrintPreviewMessageHandler& operator=(const PrintPreviewMessageHandler&) =
-      delete;
-
-  void PrintToPDF(base::DictionaryValue options,
-                  gin_helper::Promise<v8::Local<v8::Value>> promise);
-
- private:
-  friend class content::WebContentsUserData<PrintPreviewMessageHandler>;
-
-  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<v8::Local<v8::Value>> GetPromise(int request_id);
-
-  void ResolvePromise(int request_id,
-                      scoped_refptr<base::RefCountedMemory> data_bytes);
-  void RejectPromise(int request_id);
-
-  using PromiseMap = std::map<int, gin_helper::Promise<v8::Local<v8::Value>>>;
-  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<printing::mojom::PrintRenderFrame> print_render_frame_;
-
-  mojo::AssociatedReceiver<printing::mojom::PrintPreviewUI> receiver_{this};
-
-  base::WeakPtrFactory<PrintPreviewMessageHandler> weak_ptr_factory_{this};
-
-  WEB_CONTENTS_USER_DATA_KEY_DECL();
-};
-
-}  // namespace electron
-
-#endif  // ELECTRON_SHELL_BROWSER_PRINTING_PRINT_PREVIEW_MESSAGE_HANDLER_H_

+ 242 - 5
shell/browser/printing/print_view_manager_electron.cc

@@ -7,13 +7,39 @@
 #include <utility>
 
 #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<PrintViewManagerElectron>(*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<base::RefCountedString>());
+    return;
+  }
+
+  if (!rfh->IsRenderFrameLive()) {
+    std::move(callback).Run(PRINTING_FAILED,
+                            base::MakeRefCounted<base::RefCountedString>());
+    return;
+  }
+
+  absl::variant<printing::PageRanges, print_to_pdf::PageRangeError>
+      parsed_ranges = print_to_pdf::TextPageRangesToPageRanges(page_ranges);
+  if (absl::holds_alternative<print_to_pdf::PageRangeError>(parsed_ranges)) {
+    PrintResult print_result;
+    switch (absl::get<print_to_pdf::PageRangeError>(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<base::RefCountedString>());
+    return;
+  }
+
+  printing_rfh_ = rfh;
+  print_pages_params->pages = absl::get<printing::PageRanges>(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<base::RefCountedString>());
+  }
+
+  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<const char*>(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);

+ 65 - 4
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 <memory>
+#include <string>
+#include <vector>
+
+#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<PrintViewManagerElectron> {
  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<void(PrintResult,
+                              scoped_refptr<base::RefCountedMemory>)>;
+
   ~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<PrintViewManagerElectron>;
+
+  // 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<PrintViewManagerElectron>;
-  explicit PrintViewManagerElectron(content::WebContents* web_contents);
+  void Reset();
+  void ReleaseJob(PrintResult result);
+
+  raw_ptr<content::RenderFrameHost> printing_rfh_ = nullptr;
+  PrintToPDFCallback callback_;
+  std::string data_;
+  std::vector<int32_t> headless_jobs_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };

+ 9 - 10
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
         });
 

+ 4 - 3
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))
 

+ 8 - 6
spec/webview-spec.js

@@ -1110,14 +1110,16 @@ describe('<webview> tag', function () {
   ifdescribe(features.isPrintingEnabled())('<webview>.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

+ 5 - 0
typings/internal-electron.d.ts

@@ -264,6 +264,11 @@ declare namespace ElectronInternal {
     is_default?: 'true',
   }
 
+  type PageSize = {
+    width: number,
+    height: number,
+  }
+
   type ModuleLoader = () => any;
 
   interface ModuleEntry {