Skip to content

Commit

Permalink
chore(rpc): serialize rpc into actual wire string (#2740)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Jun 27, 2020
1 parent 3e33523 commit 4e94bda
Show file tree
Hide file tree
Showing 17 changed files with 198 additions and 129 deletions.
26 changes: 3 additions & 23 deletions src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
* limitations under the License.
*/

import * as fs from 'fs';
import * as mime from 'mime';
import * as path from 'path';
import * as util from 'util';
import * as frames from './frames';
import { assert, helper, assertMaxArguments } from './helper';
import InjectedScript from './injected/injectedScript';
Expand All @@ -30,6 +26,7 @@ import * as types from './types';
import { Progress } from './progress';
import DebugScript from './debug/injected/debugScript';
import { FatalDOMError, RetargetableDOMError } from './common/domErrors';
import { normalizeFilePayloads } from './rpc/serializers';

export class FrameExecutionContext extends js.ExecutionContext {
readonly frame: frames.Frame;
Expand Down Expand Up @@ -504,25 +501,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}, {}));
if (typeof multiple === 'string')
return multiple;
let ff: string[] | types.FilePayload[];
if (!Array.isArray(files))
ff = [ files ] as string[] | types.FilePayload[];
else
ff = files;
assert(multiple || ff.length <= 1, 'Non-multiple file input can only accept single file!');
const filePayloads: types.FilePayload[] = [];
for (const item of ff) {
if (typeof item === 'string') {
const file: types.FilePayload = {
name: path.basename(item),
mimeType: mime.getType(item) || 'application/octet-stream',
buffer: await util.promisify(fs.readFile)(item)
};
filePayloads.push(file);
} else {
filePayloads.push(item);
}
}
const filePayloads = await normalizeFilePayloads(files);
assert(multiple || filePayloads.length <= 1, 'Non-multiple file input can only accept single file!');
await this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.throwIfAborted(); // Avoid action that has side-effects.
await this._page._delegate.setInputFiles(this as any as ElementHandle<HTMLInputElement>, filePayloads);
Expand Down
15 changes: 0 additions & 15 deletions src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,21 +362,6 @@ export function logPolitely(toBeLogged: string) {
console.log(toBeLogged); // eslint-disable-line no-console
}

export function serializeError(e: any): types.Error {
if (e instanceof Error)
return { message: e.message, stack: e.stack };
return { value: e };
}

export function parseError(error: types.Error): any {
if (error.message !== undefined) {
const e = new Error(error.message);
e.stack = error.stack;
return e;
}
return error.value;
}

const escapeGlobChars = new Set(['/', '$', '^', '+', '.', '(', ')', '=', '!', '|']);

export const helper = Helper;
6 changes: 3 additions & 3 deletions src/rpc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export interface PageChannel extends Channel {
goForward(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null>;
opener(): Promise<PageChannel | null>;
reload(params: { options?: types.NavigateOptions }): Promise<ResponseChannel | null>;
screenshot(params: { options?: types.ScreenshotOptions }): Promise<Buffer>;
screenshot(params: { options?: types.ScreenshotOptions }): Promise<string>;
setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void>;
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
setViewportSize(params: { viewportSize: types.Size }): Promise<void>;
Expand Down Expand Up @@ -151,7 +151,7 @@ export interface FrameChannel extends Channel {
querySelectorAll(params: { selector: string }): Promise<ElementHandleChannel[]>;
selectOption(params: { selector: string, values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions }): Promise<string[]>;
setContent(params: { html: string, options: types.NavigateOptions }): Promise<void>;
setInputFiles(params: { selector: string, files: string | string[] | types.FilePayload | types.FilePayload[], options: types.NavigatingActionWaitOptions }): Promise<void>;
setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: string }[], options: types.NavigatingActionWaitOptions }): Promise<void>;
textContent(params: { selector: string, options: types.TimeoutOptions }): Promise<string | null>;
title(): Promise<string>;
type(params: { selector: string, text: string, options: { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
Expand Down Expand Up @@ -199,7 +199,7 @@ export interface ElementHandleChannel extends JSHandleChannel {
press(params: { key: string; options?: { delay?: number } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void>;
querySelector(params: { selector: string }): Promise<ElementHandleChannel | null>;
querySelectorAll(params: { selector: string }): Promise<ElementHandleChannel[]>;
screenshot(params: { options?: types.ElementScreenshotOptions }): Promise<Buffer>;
screenshot(params: { options?: types.ElementScreenshotOptions }): Promise<string>;
scrollIntoViewIfNeeded(params: { options?: types.TimeoutOptions }): Promise<void>;
selectOption(params: { values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null; options?: types.NavigatingActionWaitOptions }): string[] | Promise<string[]>;
selectText(params: { options?: types.TimeoutOptions }): Promise<void>;
Expand Down
6 changes: 4 additions & 2 deletions src/rpc/client/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
super(connection, channel, initializer);
}

async newContext(options?: types.BrowserContextOptions): Promise<BrowserContext> {
async newContext(options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
delete (options as any).logger;
const context = BrowserContext.from(await this._channel.newContext({ options }));
this._contexts.add(context);
context._browser = this;
Expand All @@ -49,7 +50,8 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
return [...this._contexts];
}

async newPage(options?: types.BrowserContextOptions): Promise<Page> {
async newPage(options: types.BrowserContextOptions = {}): Promise<Page> {
delete (options as any).logger;
const context = await this.newContext(options);
const page = await context.newPage();
page._ownedContext = context;
Expand Down
7 changes: 5 additions & 2 deletions src/rpc/client/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,18 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
return this._initializer.name;
}

async launch(options?: types.LaunchOptions): Promise<Browser> {
async launch(options: types.LaunchOptions = {}): Promise<Browser> {
delete (options as any).logger;
return Browser.from(await this._channel.launch({ options }));
}

async launchPersistentContext(userDataDir: string, options?: types.LaunchOptions & types.BrowserContextOptions): Promise<BrowserContext> {
async launchPersistentContext(userDataDir: string, options: types.LaunchOptions & types.BrowserContextOptions = {}): Promise<BrowserContext> {
delete (options as any).logger;
return BrowserContext.from(await this._channel.launchPersistentContext({ userDataDir, options }));
}

async connect(options: types.ConnectOptions): Promise<Browser> {
delete (options as any).logger;
return Browser.from(await this._channel.connect({ options }));
}
}
8 changes: 4 additions & 4 deletions src/rpc/client/elementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as types from '../../types';
import { ElementHandleChannel, JSHandleInitializer } from '../channels';
import { Frame } from './frame';
import { FuncOn, JSHandle, convertArg } from './jsHandle';
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
import { Connection } from '../connection';

export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
Expand Down Expand Up @@ -125,7 +125,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
}

async screenshot(options?: types.ElementScreenshotOptions): Promise<Buffer> {
return await this._elementChannel.screenshot({ options });
return Buffer.from(await this._elementChannel.screenshot({ options }), 'base64');
}

async $(selector: string): Promise<ElementHandle<Element> | null> {
Expand All @@ -139,13 +139,13 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
return await this._elementChannel.$evalExpression({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return parseResult(await this._elementChannel.$evalExpression({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }));
}

async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
return await this._elementChannel.$$evalExpression({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return parseResult(await this._elementChannel.$$evalExpression({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }));
}
}

Expand Down
16 changes: 9 additions & 7 deletions src/rpc/client/frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import { FrameChannel, FrameInitializer } from '../channels';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { ElementHandle, convertSelectOptionValues } from './elementHandle';
import { JSHandle, Func1, FuncOn, SmartHandle, convertArg } from './jsHandle';
import { JSHandle, Func1, FuncOn, SmartHandle, serializeArgument, parseResult } from './jsHandle';
import * as network from './network';
import { Response } from './network';
import { Page } from './page';
import { Connection } from '../connection';
import { normalizeFilePayloads } from '../serializers';

export type GotoOptions = types.NavigateOptions & {
referer?: string,
Expand Down Expand Up @@ -78,14 +79,14 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
async evaluateHandle<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
assertMaxArguments(arguments.length, 2);
return JSHandle.from(await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) })) as SmartHandle<R>;
return JSHandle.from(await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) })) as SmartHandle<R>;
}

async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2);
return await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return parseResult(await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }));
}

async $(selector: string): Promise<ElementHandle<Element> | null> {
Expand All @@ -104,14 +105,14 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3);
return await this._channel.$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return await this._channel.$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
}

async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3);
return await this._channel.$$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return await this._channel.$$eval({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
}

async $$(selector: string): Promise<ElementHandle<Element>[]> {
Expand Down Expand Up @@ -196,7 +197,8 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
}

async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
await this._channel.setInputFiles({ selector, files, options });
const filePayloads = await normalizeFilePayloads(files);
await this._channel.setInputFiles({ selector, files: filePayloads.map(f => ({ name: f.name, mimeType: f.mimeType, buffer: f.buffer.toString('base64') })), options });
}

async type(selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
Expand All @@ -222,7 +224,7 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R>(pageFunction: Func1<void, R>, arg?: any, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options: types.WaitForFunctionOptions = {}): Promise<SmartHandle<R>> {
return JSHandle.from(await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg, options })) as SmartHandle<R>;
return JSHandle.from(await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), options })) as SmartHandle<R>;
}

async title(): Promise<string> {
Expand Down
35 changes: 19 additions & 16 deletions src/rpc/client/jsHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { ElementHandle } from './elementHandle';
import { ChannelOwner } from './channelOwner';
import { Connection } from '../connection';
import { serializeAsCallArgument, parseEvaluationResultValue } from '../../common/utilityScriptSerializers';

type NoHandles<Arg> = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles<Arg[Key]> } : Arg);
type Unboxed<Arg> =
Expand Down Expand Up @@ -51,13 +52,13 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleIni
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<R> {
return await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
return parseResult(await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }));
}

async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: FuncOn<T, void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
const handleChannel = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: convertArg(arg) });
const handleChannel = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return JSHandle.from(handleChannel) as SmartHandle<R>;
}

Expand Down Expand Up @@ -97,18 +98,20 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleIni
}
}

export function convertArg(arg: any): any {
if (arg === null)
return null;
if (Array.isArray(arg))
return arg.map(item => convertArg(item));
if (arg instanceof ChannelOwner)
return arg._channel;
if (typeof arg === 'object') {
const result: any = {};
for (const key of Object.keys(arg))
result[key] = convertArg(arg[key]);
return result;
}
return arg;
export function serializeArgument(arg: any): any {
const guids: { guid: string }[] = [];
const pushHandle = (guid: string): number => {
guids.push({ guid });
return guids.length - 1;
};
const value = serializeAsCallArgument(arg, value => {
if (value instanceof ChannelOwner)
return { h: pushHandle(value._channel._guid) };
return { fallThrough: value };
});
return { value, guids };
}

export function parseResult(arg: any): any {
return parseEvaluationResultValue(arg, []);
}
5 changes: 3 additions & 2 deletions src/rpc/client/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import { EventEmitter } from 'events';
import { Events } from '../../events';
import { assert, assertMaxArguments, helper, Listener, serializeError, parseError } from '../../helper';
import { assert, assertMaxArguments, helper, Listener } from '../../helper';
import * as types from '../../types';
import { PageChannel, BindingCallChannel, Channel, PageInitializer, BindingCallInitializer } from '../channels';
import { BrowserContext } from './browserContext';
Expand All @@ -34,6 +34,7 @@ import { Dialog } from './dialog';
import { Download } from './download';
import { TimeoutError } from '../../errors';
import { TimeoutSettings } from '../../timeoutSettings';
import { parseError, serializeError } from '../serializers';

export class Page extends ChannelOwner<PageChannel, PageInitializer> {
readonly pdf: ((options?: types.PDFOptions) => Promise<Buffer>) | undefined;
Expand Down Expand Up @@ -365,7 +366,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
}

async screenshot(options?: types.ScreenshotOptions): Promise<Buffer> {
return await this._channel.screenshot({ options });
return Buffer.from(await this._channel.screenshot({ options }), 'base64');
}

async title(): Promise<string> {
Expand Down
Loading

0 comments on commit 4e94bda

Please sign in to comment.