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:
parent
79d5991a27
commit
29504c0824
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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[],
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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) });
|
||||
|
|
|
|||
|
|
@ -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 }> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue