feat(rpc): make SerializedValue format pdl-friendly (#3007)

This avoids sum types and instead uses different fields
for different types.
This commit is contained in:
Dmitry Gozman 2020-07-19 19:46:19 -07:00 committed by GitHub
parent 79d5991a27
commit 29504c0824
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 34 additions and 33 deletions

View file

@ -18,9 +18,9 @@ export type SerializedValue =
undefined | boolean | number | string |
{ v: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0' } |
{ d: string } |
{ r: [string, string] } |
{ r: { p: string, f: string} } |
{ a: SerializedValue[] } |
{ o: { [key: string]: SerializedValue } } |
{ o: { k: string, v: SerializedValue }[] } |
{ h: number };
function isRegExp(obj: any): obj is RegExp {
@ -36,9 +36,9 @@ function isError(obj: any): obj is Error {
}
export function parseEvaluationResultValue(value: SerializedValue, handles: any[] = []): any {
if (value === undefined)
if (Object.is(value, undefined))
return undefined;
if (typeof value === 'object') {
if (typeof value === 'object' && value) {
if ('v' in value) {
if (value.v === 'undefined')
return undefined;
@ -52,17 +52,18 @@ export function parseEvaluationResultValue(value: SerializedValue, handles: any[
return -Infinity;
if (value.v === '-0')
return -0;
return undefined;
}
if ('d' in value)
return new Date(value.d);
if ('r' in value)
return new RegExp(value.r[0], value.r[1]);
return new RegExp(value.r.p, value.r.f);
if ('a' in value)
return value.a.map((a: any) => parseEvaluationResultValue(a, handles));
if ('o' in value) {
const result: any = {};
for (const name of Object.keys(value.o))
result[name] = parseEvaluationResultValue(value.o[name], handles);
for (const { k, v } of value.o)
result[k] = parseEvaluationResultValue(v, handles);
return result;
}
if ('h' in value)
@ -72,12 +73,12 @@ export function parseEvaluationResultValue(value: SerializedValue, handles: any[
}
export type HandleOrValue = { h: number } | { fallThrough: any };
export function serializeAsCallArgument(value: any, jsHandleSerializer: (value: any) => HandleOrValue): SerializedValue {
return serialize(value, jsHandleSerializer, new Set());
export function serializeAsCallArgument(value: any, handleSerializer: (value: any) => HandleOrValue): SerializedValue {
return serialize(value, handleSerializer, new Set());
}
function serialize(value: any, jsHandleSerializer: (value: any) => HandleOrValue, visited: Set<any>): SerializedValue {
const result = jsHandleSerializer(value);
function serialize(value: any, handleSerializer: (value: any) => HandleOrValue, visited: Set<any>): SerializedValue {
const result = handleSerializer(value);
if ('fallThrough' in result)
value = result.fallThrough;
else
@ -111,26 +112,26 @@ function serialize(value: any, jsHandleSerializer: (value: any) => HandleOrValue
const error = value;
if ('captureStackTrace' in global.Error) {
// v8
return error.stack;
return error.stack || '';
}
return `${error.name}: ${error.message}\n${error.stack}`;
}
if (isDate(value))
return { d: value.toJSON() };
if (isRegExp(value))
return { r: [ value.source, value.flags ] };
return { r: { p: value.source, f: value.flags } };
if (Array.isArray(value)) {
const result = [];
const a = [];
visited.add(value);
for (let i = 0; i < value.length; ++i)
result.push(serialize(value[i], jsHandleSerializer, visited));
a.push(serialize(value[i], handleSerializer, visited));
visited.delete(value);
return { a: result };
return { a };
}
if (typeof value === 'object') {
const result: any = {};
const o: { k: string, v: SerializedValue }[] = [];
visited.add(value);
for (const name of Object.keys(value)) {
let item;
@ -140,11 +141,11 @@ function serialize(value: any, jsHandleSerializer: (value: any) => HandleOrValue
continue; // native bindings will throw sometimes
}
if (name === 'toJSON' && typeof item === 'function')
result[name] = {};
o.push({ k: name, v: { o: [] } });
else
result[name] = serialize(item, jsHandleSerializer, visited);
o.push({ k: name, v: serialize(item, handleSerializer, visited) });
}
visited.delete(value);
return { o: result };
return { o };
}
}

View file

@ -22,10 +22,10 @@ export default class UtilityScript {
return returnByValue ? this._promiseAwareJsonValueNoThrow(result) : result;
}
callFunction(returnByValue: boolean, functionText: string, ...args: any[]) {
const argCount = args[0] as number;
const handles = args.slice(argCount + 1);
const parameters = args.slice(1, argCount + 1).map(a => parseEvaluationResultValue(a, handles));
callFunction(returnByValue: boolean, functionText: string, argCount: number, ...argsAndHandles: any[]) {
const args = argsAndHandles.slice(0, argCount);
const handles = argsAndHandles.slice(argCount);
const parameters = args.map(a => parseEvaluationResultValue(a, handles));
const func = global.eval('(' + functionText + ')');
const result = func(...parameters);
return returnByValue ? this._promiseAwareJsonValueNoThrow(result) : result;

View file

@ -367,8 +367,7 @@ export interface BindingCallChannel extends Channel {
export type BindingCallInitializer = {
frame: FrameChannel,
name: string,
// TODO: migrate this to SerializedArgument.
args: any[]
args: SerializedValue[],
};

View file

@ -32,7 +32,7 @@ import { ElementHandle } from './elementHandle';
import { Worker } from './worker';
import { Frame, FunctionWithSource, GotoOptions } from './frame';
import { Keyboard, Mouse } from './input';
import { Func1, FuncOn, SmartHandle, serializeArgument } from './jsHandle';
import { Func1, FuncOn, SmartHandle, serializeArgument, parseResult } from './jsHandle';
import { Request, Response, Route, RouteHandler } from './network';
import { FileChooser } from './fileChooser';
import { Buffer } from 'buffer';
@ -557,7 +557,7 @@ export class BindingCall extends ChannelOwner<BindingCallChannel, BindingCallIni
page: frame._page!,
frame
};
const result = await func(source, ...this._initializer.args);
const result = await func(source, ...this._initializer.args.map(parseResult));
this._channel.resolve({ result: serializeArgument(result) });
} catch (e) {
this._channel.reject({ error: serializeError(e) });

View file

@ -20,7 +20,7 @@ import { ElectronApplicationChannel, ElectronApplicationInitializer, PageChannel
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserContextBase } from '../../browserContext';
import { PageDispatcher } from './pageDispatcher';
import { parseArgument } from './jsHandleDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { createHandle } from './elementHandlerDispatcher';
import { SerializedValue } from '../../common/utilityScriptSerializers';
@ -57,7 +57,7 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
const handle = this._object._nodeElectronHandle!;
return { value: await handle._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg)) };
return { value: serializeResult(await handle._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg))) };
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }> {

View file

@ -251,7 +251,7 @@ export class BindingCallDispatcher extends Dispatcher<{}, BindingCallInitializer
super(scope, {}, 'bindingCall', {
frame: lookupDispatcher<FrameDispatcher>(source.frame),
name,
args
args: args.map(serializeResult),
});
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve;

View file

@ -17,7 +17,7 @@
const utils = require('./utils');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS, CHANNEL} = testOptions;
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = testOptions;
describe('Page.evaluate', function() {
it('should work', async({page, server}) => {
@ -373,7 +373,8 @@ describe('Page.evaluate', function() {
});
expect(result).toBe(undefined);
});
it.skip(CHANNEL)('should transfer 100Mb of data from page to node.js', async({page}) => {
it.fail(USES_HOOKS)('should transfer 100Mb of data from page to node.js', async({page}) => {
// This does not use hooks, but is slow in wire channel.
const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a'));
expect(a.length).toBe(100 * 1024 * 1024);
});