feat: promisify webContents.printToPDF() (#16795)

This commit is contained in:
Milan Burda 2019-02-11 20:20:04 +01:00 коммит произвёл John Kleinschmidt
Родитель 3effa6f20c
Коммит 36ce3e9546
12 изменённых файлов: 161 добавлений и 57 удалений

Просмотреть файл

@ -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(() => {