feat: promisify webContents.printToPDF() (#16795)
This commit is contained in:
Родитель
3effa6f20c
Коммит
36ce3e9546
|
@ -1499,11 +1499,12 @@ std::vector<printing::PrinterBasicInfo> WebContents::GetPrinterList() {
|
|||
return printers;
|
||||
}
|
||||
|
||||
void WebContents::PrintToPDF(
|
||||
const base::DictionaryValue& settings,
|
||||
const PrintPreviewMessageHandler::PrintToPDFCallback& callback) {
|
||||
v8::Local<v8::Promise> WebContents::PrintToPDF(
|
||||
const base::DictionaryValue& settings) {
|
||||
scoped_refptr<util::Promise> promise = new util::Promise(isolate());
|
||||
PrintPreviewMessageHandler::FromWebContents(web_contents())
|
||||
->PrintToPDF(settings, callback);
|
||||
->PrintToPDF(settings, promise);
|
||||
return promise->GetHandle();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -174,9 +174,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
|||
void Print(mate::Arguments* args);
|
||||
std::vector<printing::PrinterBasicInfo> GetPrinterList();
|
||||
// Print current page as PDF.
|
||||
void PrintToPDF(
|
||||
const base::DictionaryValue& settings,
|
||||
const PrintPreviewMessageHandler::PrintToPDFCallback& callback);
|
||||
v8::Local<v8::Promise> PrintToPDF(const base::DictionaryValue& settings);
|
||||
#endif
|
||||
|
||||
// DevTools workspace api.
|
||||
|
|
|
@ -89,7 +89,7 @@ void PrintPreviewMessageHandler::OnMetafileReadyForPrinting(
|
|||
const PrintHostMsg_DidPrintContent_Params& content = params.content;
|
||||
if (!content.metafile_data_region.IsValid() ||
|
||||
params.expected_pages_count <= 0) {
|
||||
RunPrintToPDFCallback(ids.request_id, nullptr);
|
||||
RejectPromise(ids.request_id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -102,10 +102,9 @@ void PrintPreviewMessageHandler::OnMetafileReadyForPrinting(
|
|||
base::BindOnce(&PrintPreviewMessageHandler::OnCompositePdfDocumentDone,
|
||||
weak_ptr_factory_.GetWeakPtr(), ids));
|
||||
} else {
|
||||
RunPrintToPDFCallback(
|
||||
ids.request_id,
|
||||
base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(
|
||||
content.metafile_data_region));
|
||||
ResolvePromise(ids.request_id,
|
||||
base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(
|
||||
content.metafile_data_region));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,11 +116,11 @@ void PrintPreviewMessageHandler::OnCompositePdfDocumentDone(
|
|||
|
||||
if (status != printing::mojom::PdfCompositor::Status::SUCCESS) {
|
||||
DLOG(ERROR) << "Compositing pdf failed with error " << status;
|
||||
RunPrintToPDFCallback(ids.request_id, nullptr);
|
||||
RejectPromise(ids.request_id);
|
||||
return;
|
||||
}
|
||||
|
||||
RunPrintToPDFCallback(
|
||||
ResolvePromise(
|
||||
ids.request_id,
|
||||
base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(region));
|
||||
}
|
||||
|
@ -131,7 +130,7 @@ void PrintPreviewMessageHandler::OnPrintPreviewFailed(
|
|||
const PrintHostMsg_PreviewIds& ids) {
|
||||
StopWorker(document_cookie);
|
||||
|
||||
RunPrintToPDFCallback(ids.request_id, nullptr);
|
||||
RejectPromise(ids.request_id);
|
||||
}
|
||||
|
||||
void PrintPreviewMessageHandler::OnPrintPreviewCancelled(
|
||||
|
@ -139,15 +138,15 @@ void PrintPreviewMessageHandler::OnPrintPreviewCancelled(
|
|||
const PrintHostMsg_PreviewIds& ids) {
|
||||
StopWorker(document_cookie);
|
||||
|
||||
RunPrintToPDFCallback(ids.request_id, nullptr);
|
||||
RejectPromise(ids.request_id);
|
||||
}
|
||||
|
||||
void PrintPreviewMessageHandler::PrintToPDF(
|
||||
const base::DictionaryValue& options,
|
||||
const PrintToPDFCallback& callback) {
|
||||
scoped_refptr<atom::util::Promise> promise) {
|
||||
int request_id;
|
||||
options.GetInteger(printing::kPreviewRequestID, &request_id);
|
||||
print_to_pdf_callback_map_[request_id] = callback;
|
||||
promise_map_[request_id] = std::move(promise);
|
||||
|
||||
auto* focused_frame = web_contents()->GetFocusedFrame();
|
||||
auto* rfh = focused_frame && focused_frame->HasSelection()
|
||||
|
@ -156,28 +155,44 @@ void PrintPreviewMessageHandler::PrintToPDF(
|
|||
rfh->Send(new PrintMsg_PrintPreview(rfh->GetRoutingID(), options));
|
||||
}
|
||||
|
||||
void PrintPreviewMessageHandler::RunPrintToPDFCallback(
|
||||
scoped_refptr<atom::util::Promise> PrintPreviewMessageHandler::GetPromise(
|
||||
int request_id) {
|
||||
auto it = promise_map_.find(request_id);
|
||||
DCHECK(it != promise_map_.end());
|
||||
|
||||
auto promise = it->second;
|
||||
promise_map_.erase(it);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
void PrintPreviewMessageHandler::ResolvePromise(
|
||||
int request_id,
|
||||
scoped_refptr<base::RefCountedMemory> data_bytes) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
|
||||
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||
v8::Locker locker(isolate);
|
||||
auto promise = GetPromise(request_id);
|
||||
|
||||
v8::Isolate* isolate = promise->isolate();
|
||||
mate::Locker locker(isolate);
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
if (data_bytes && data_bytes->size()) {
|
||||
v8::Local<v8::Value> buffer =
|
||||
node::Buffer::Copy(isolate,
|
||||
reinterpret_cast<const char*>(data_bytes->front()),
|
||||
data_bytes->size())
|
||||
.ToLocalChecked();
|
||||
print_to_pdf_callback_map_[request_id].Run(v8::Null(isolate), buffer);
|
||||
} else {
|
||||
v8::Local<v8::String> error_message =
|
||||
v8::String::NewFromUtf8(isolate, "Failed to generate PDF");
|
||||
print_to_pdf_callback_map_[request_id].Run(
|
||||
v8::Exception::Error(error_message), v8::Null(isolate));
|
||||
}
|
||||
print_to_pdf_callback_map_.erase(request_id);
|
||||
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_bytes->front()),
|
||||
data_bytes->size())
|
||||
.ToLocalChecked();
|
||||
|
||||
promise->Resolve(buffer);
|
||||
}
|
||||
|
||||
void PrintPreviewMessageHandler::RejectPromise(int request_id) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
|
||||
auto promise = GetPromise(request_id);
|
||||
promise->RejectWithErrorMessage("Failed to generate PDF");
|
||||
}
|
||||
|
||||
} // namespace atom
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <map>
|
||||
|
||||
#include "atom/common/promise_util.h"
|
||||
#include "base/memory/ref_counted_memory.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom.h"
|
||||
|
@ -28,13 +29,10 @@ class PrintPreviewMessageHandler
|
|||
: public content::WebContentsObserver,
|
||||
public content::WebContentsUserData<PrintPreviewMessageHandler> {
|
||||
public:
|
||||
using PrintToPDFCallback =
|
||||
base::Callback<void(v8::Local<v8::Value>, v8::Local<v8::Value>)>;
|
||||
|
||||
~PrintPreviewMessageHandler() override;
|
||||
|
||||
void PrintToPDF(const base::DictionaryValue& options,
|
||||
const PrintToPDFCallback& callback);
|
||||
scoped_refptr<atom::util::Promise> promise);
|
||||
|
||||
protected:
|
||||
// content::WebContentsObserver implementation.
|
||||
|
@ -57,11 +55,15 @@ class PrintPreviewMessageHandler
|
|||
const PrintHostMsg_PreviewIds& ids);
|
||||
void OnPrintPreviewCancelled(int document_cookie,
|
||||
const PrintHostMsg_PreviewIds& ids);
|
||||
void RunPrintToPDFCallback(int request_id,
|
||||
scoped_refptr<base::RefCountedMemory> data_bytes);
|
||||
|
||||
using PrintToPDFCallbackMap = std::map<int, PrintToPDFCallback>;
|
||||
PrintToPDFCallbackMap print_to_pdf_callback_map_;
|
||||
scoped_refptr<atom::util::Promise> 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, scoped_refptr<atom::util::Promise>>;
|
||||
PromiseMap promise_map_;
|
||||
|
||||
base::WeakPtrFactory<PrintPreviewMessageHandler> weak_ptr_factory_;
|
||||
|
||||
|
|
|
@ -28,17 +28,16 @@ When a majority of affected functions are migrated, this flag will be enabled by
|
|||
- [ses.clearAuthCache(options[, callback])](https://github.com/electron/electron/blob/master/docs/api/session.md#clearAuthCache)
|
||||
- [contents.executeJavaScript(code[, userGesture, callback])](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#executeJavaScript)
|
||||
- [contents.print([options], [callback])](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#print)
|
||||
- [contents.printToPDF(options, callback)](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#printToPDF)
|
||||
- [contents.savePage(fullPath, saveType, callback)](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#savePage)
|
||||
- [webFrame.executeJavaScript(code[, userGesture, callback])](https://github.com/electron/electron/blob/master/docs/api/web-frame.md#executeJavaScript)
|
||||
- [webFrame.executeJavaScriptInIsolatedWorld(worldId, scripts[, userGesture, callback])](https://github.com/electron/electron/blob/master/docs/api/web-frame.md#executeJavaScriptInIsolatedWorld)
|
||||
- [webviewTag.executeJavaScript(code[, userGesture, callback])](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#executeJavaScript)
|
||||
- [webviewTag.printToPDF(options, callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#printToPDF)
|
||||
|
||||
### Converted Functions
|
||||
|
||||
- [app.getFileIcon(path[, options], callback)](https://github.com/electron/electron/blob/master/docs/api/app.md#getFileIcon)
|
||||
- [contents.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#capturePage)
|
||||
- [contents.printToPDF(options, callback)](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#printToPDF)
|
||||
- [contentTracing.getCategories(callback)](https://github.com/electron/electron/blob/master/docs/api/content-tracing.md#getCategories)
|
||||
- [contentTracing.startRecording(options, callback)](https://github.com/electron/electron/blob/master/docs/api/content-tracing.md#startRecording)
|
||||
- [contentTracing.stopRecording(resultFilePath, callback)](https://github.com/electron/electron/blob/master/docs/api/content-tracing.md#stopRecording)
|
||||
|
@ -50,5 +49,6 @@ When a majority of affected functions are migrated, this flag will be enabled by
|
|||
- [protocol.isProtocolHandled(scheme, callback)](https://github.com/electron/electron/blob/master/docs/api/protocol.md#isProtocolHandled)
|
||||
- [shell.openExternal(url[, options, callback])](https://github.com/electron/electron/blob/master/docs/api/shell.md#openExternal)
|
||||
- [webviewTag.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#capturePage)
|
||||
- [webviewTag.printToPDF(options, callback)](https://github.com/electron/electron/blob/master/docs/api/webview-tag.md#printToPDF)
|
||||
- [win.capturePage([rect, ]callback)](https://github.com/electron/electron/blob/master/docs/api/browser-window.md#capturePage)
|
||||
- [desktopCapturer.getSources(options, callback)](https://github.com/electron/electron/blob/master/docs/api/desktop-capturer.md#getSources)
|
||||
|
|
|
@ -1202,6 +1202,25 @@ settings.
|
|||
The `callback` will be called with `callback(error, data)` on completion. The
|
||||
`data` is a `Buffer` that contains the generated PDF data.
|
||||
|
||||
**[Deprecated Soon](promisification.md)**
|
||||
|
||||
#### `contents.printToPDF(options)`
|
||||
|
||||
* `options` Object
|
||||
* `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for
|
||||
default margin, 1 for no margin, and 2 for minimum margin.
|
||||
* `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) - `true` for landscape, `false` for portrait.
|
||||
|
||||
* Returns `Promise<Buffer>` - Resolves with the generated PDF data.
|
||||
|
||||
Prints window's web page as PDF with Chromium's preview printing custom
|
||||
settings.
|
||||
|
||||
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:
|
||||
|
|
|
@ -542,6 +542,24 @@ Prints `webview`'s web page. Same as `webContents.print([options])`.
|
|||
|
||||
Prints `webview`'s web page as PDF, Same as `webContents.printToPDF(options, callback)`.
|
||||
|
||||
**[Deprecated Soon](promisification.md)**
|
||||
|
||||
### `<webview>.printToPDF(options)`
|
||||
|
||||
* `options` Object
|
||||
* `marginsType` Integer (optional) - Specifies the type of margins to use. Uses 0 for
|
||||
default margin, 1 for no margin, and 2 for minimum margin.
|
||||
* `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) - `true` for landscape, `false` for portrait.
|
||||
|
||||
* Returns `Promise<Buffer>` - Resolves with the generated PDF data.
|
||||
|
||||
Prints `webview`'s web page as PDF, Same as `webContents.printToPDF(options)`.
|
||||
|
||||
### `<webview>.capturePage([rect, ]callback)`
|
||||
|
||||
* `rect` [Rectangle](structures/rectangle.md) (optional) - The bounds to capture
|
||||
|
|
|
@ -263,7 +263,7 @@ WebContents.prototype.takeHeapSnapshot = function (filePath) {
|
|||
}
|
||||
|
||||
// Translate the options of printToPDF.
|
||||
WebContents.prototype.printToPDF = function (options, callback) {
|
||||
WebContents.prototype.printToPDF = function (options) {
|
||||
const printingSetting = Object.assign({}, defaultPrintingSetting)
|
||||
if (options.landscape) {
|
||||
printingSetting.landscape = options.landscape
|
||||
|
@ -282,7 +282,7 @@ WebContents.prototype.printToPDF = function (options, callback) {
|
|||
const pageSize = options.pageSize
|
||||
if (typeof pageSize === 'object') {
|
||||
if (!pageSize.height || !pageSize.width) {
|
||||
return callback(new Error('Must define height and width for pageSize'))
|
||||
return Promise.reject(new Error('Must define height and width for pageSize'))
|
||||
}
|
||||
// Dimensions in Microns
|
||||
// 1 meter = 10^6 microns
|
||||
|
@ -295,7 +295,7 @@ WebContents.prototype.printToPDF = function (options, callback) {
|
|||
} else if (PDFPageSizes[pageSize]) {
|
||||
printingSetting.mediaSize = PDFPageSizes[pageSize]
|
||||
} else {
|
||||
return callback(new Error(`Does not support pageSize with ${pageSize}`))
|
||||
return Promise.reject(new Error(`Does not support pageSize with ${pageSize}`))
|
||||
}
|
||||
} else {
|
||||
printingSetting.mediaSize = PDFPageSizes['A4']
|
||||
|
@ -304,9 +304,9 @@ WebContents.prototype.printToPDF = function (options, callback) {
|
|||
// Chromium expects this in a 0-100 range number, not as float
|
||||
printingSetting.scaleFactor *= 100
|
||||
if (features.isPrintingEnabled()) {
|
||||
this._printToPDF(printingSetting, callback)
|
||||
return this._printToPDF(printingSetting)
|
||||
} else {
|
||||
console.error('Error: Printing feature is disabled.')
|
||||
return Promise.reject(new Error('Printing feature is disabled'))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,6 +342,9 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
|||
}))
|
||||
}
|
||||
|
||||
WebContents.prototype.capturePage = deprecate.promisify(WebContents.prototype.capturePage)
|
||||
WebContents.prototype.printToPDF = deprecate.promisify(WebContents.prototype.printToPDF)
|
||||
|
||||
const addReplyToEvent = (event) => {
|
||||
event.reply = (...args) => {
|
||||
event.sender.sendToFrame(event.frameId, ...args)
|
||||
|
@ -385,8 +388,6 @@ WebContents.prototype._init = function () {
|
|||
// render-view-deleted event, so ignore the listeners warning.
|
||||
this.setMaxListeners(0)
|
||||
|
||||
this.capturePage = deprecate.promisify(this.capturePage)
|
||||
|
||||
// Dispatch IPC messages to the ipc module.
|
||||
this.on('-ipc-message', function (event, internal, channel, args) {
|
||||
if (internal) {
|
||||
|
|
|
@ -60,11 +60,11 @@ exports.asyncCallbackMethods = new Set([
|
|||
'sendInputEvent',
|
||||
'setLayoutZoomLevelLimits',
|
||||
'setVisualZoomLevelLimits',
|
||||
'print',
|
||||
'printToPDF'
|
||||
'print'
|
||||
])
|
||||
|
||||
exports.asyncPromiseMethods = new Set([
|
||||
'capturePage',
|
||||
'executeJavaScript'
|
||||
'executeJavaScript',
|
||||
'printToPDF'
|
||||
])
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict'
|
||||
|
||||
const { webFrame } = require('electron')
|
||||
const { webFrame, deprecate } = require('electron')
|
||||
|
||||
const v8Util = process.atomBinding('v8_util')
|
||||
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
|
||||
|
@ -287,6 +287,8 @@ const setupMethods = (WebViewElement) => {
|
|||
for (const method of asyncPromiseMethods) {
|
||||
WebViewElement.prototype[method] = createPromiseHandler(method)
|
||||
}
|
||||
|
||||
WebViewElement.prototype.printToPDF = deprecate.promisify(WebViewElement.prototype.printToPDF)
|
||||
}
|
||||
|
||||
module.exports = { setupAttributes, setupMethods, guestViewInternal, webFrame, WebViewImpl }
|
||||
|
|
|
@ -1268,7 +1268,22 @@ describe('webContents module', () => {
|
|||
}
|
||||
})
|
||||
|
||||
it('can print to PDF', (done) => {
|
||||
it('can print to PDF', async () => {
|
||||
w.destroy()
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
sandbox: true
|
||||
}
|
||||
})
|
||||
await w.loadURL('data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E')
|
||||
const data = await w.webContents.printToPDF({})
|
||||
assert.strictEqual(data instanceof Buffer, true)
|
||||
assert.notStrictEqual(data.length, 0)
|
||||
})
|
||||
|
||||
// TODO(miniak): remove when promisification is complete
|
||||
it('can print to PDF (callback)', (done) => {
|
||||
w.destroy()
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
|
|
|
@ -12,6 +12,7 @@ const { emittedOnce, waitForEvent } = require('./events-helpers')
|
|||
const { expect } = chai
|
||||
chai.use(dirtyChai)
|
||||
|
||||
const features = process.atomBinding('features')
|
||||
const isCI = remote.getGlobal('isCi')
|
||||
const nativeModulesEnabled = remote.getGlobal('nativeModulesEnabled')
|
||||
|
||||
|
@ -1233,6 +1234,38 @@ describe('<webview> tag', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('<webview>.printToPDF()', () => {
|
||||
before(function () {
|
||||
if (!features.isPrintingEnabled()) {
|
||||
this.skip()
|
||||
}
|
||||
})
|
||||
|
||||
it('can print to PDF', async () => {
|
||||
const src = 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E'
|
||||
await loadWebView(webview, { src })
|
||||
|
||||
const data = await webview.printToPDF({})
|
||||
assert.strictEqual(data instanceof Buffer, true)
|
||||
assert.notStrictEqual(data.length, 0)
|
||||
})
|
||||
|
||||
// TODO(miniak): remove when promisification is complete
|
||||
it('can print to PDF (callback)', (done) => {
|
||||
webview.addEventListener('did-finish-load', () => {
|
||||
webview.printToPDF({}, function (error, data) {
|
||||
assert.strictEqual(error, null)
|
||||
assert.strictEqual(data instanceof Buffer, true)
|
||||
assert.notStrictEqual(data.length, 0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
webview.src = 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E'
|
||||
document.body.appendChild(webview)
|
||||
})
|
||||
})
|
||||
|
||||
// FIXME(deepak1556): Ch69 follow up.
|
||||
xdescribe('document.visibilityState/hidden', () => {
|
||||
afterEach(() => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче