chore(rpc): serialize rpc into actual wire string (#2740)

This commit is contained in:
Pavel Feldman 2020-06-27 11:10:07 -07:00 committed by GitHub
parent 3e33523ee3
commit 4e94bdabfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 198 additions and 129 deletions

View File

@ -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';
@ -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;
@ -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);

View File

@ -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;

View File

@ -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>;
@ -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>;
@ -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>;

View File

@ -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;
@ -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;

View File

@ -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 }));
}
}

View File

@ -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> {
@ -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> {
@ -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) }));
}
}

View File

@ -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,
@ -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> {
@ -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>[]> {
@ -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 = {}) {
@ -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> {

View File

@ -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> =
@ -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>;
}
@ -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, []);
}

View File

@ -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';
@ -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;
@ -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> {

View File

@ -29,11 +29,14 @@ import { Channel } from './channels';
import { ConsoleMessage } from './client/consoleMessage';
import { Dialog } from './client/dialog';
import { Download } from './client/download';
import { parseError } from './serializers';
export class Connection {
private _channels = new Map<string, Channel>();
private _waitingForObject = new Map<string, any>();
sendMessageToServerTransport = (message: any): Promise<any> => Promise.resolve();
sendMessageToServerTransport = (message: string): void => {};
private _lastId = 0;
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void }>();
constructor() {}
@ -103,23 +106,33 @@ export class Connection {
return new Promise(f => this._waitingForObject.set(guid, f));
}
async sendMessageToServer(message: { guid: string, method: string, params: any }) {
const converted = {...message, params: this._replaceChannelsWithGuids(message.params)};
async sendMessageToServer(message: { guid: string, method: string, params: any }): Promise<any> {
const id = ++this._lastId;
const converted = { id, ...message, params: this._replaceChannelsWithGuids(message.params) };
debug('pw:channel:command')(converted);
const response = await this.sendMessageToServerTransport(converted);
debug('pw:channel:response')(response);
return this._replaceGuidsWithChannels(response);
this.sendMessageToServerTransport(JSON.stringify(converted));
return new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject }));
}
dispatchMessageFromServer(message: { guid: string, method: string, params: any }) {
debug('pw:channel:event')(message);
const { guid, method, params } = message;
dispatchMessageFromServer(message: string) {
const parsedMessage = JSON.parse(message);
const { id, guid, method, params, result, error } = parsedMessage;
if (id) {
debug('pw:channel:response')(parsedMessage);
const callback = this._callbacks.get(id)!;
this._callbacks.delete(id);
if (error)
callback.reject(parseError(error));
else
callback.resolve(this._replaceGuidsWithChannels(result));
return;
}
debug('pw:channel:event')(parsedMessage);
if (method === '__create__') {
this._createRemoteObject(params.type, guid, params.initializer);
return;
}
const channel = this._channels.get(guid)!;
channel.emit(method, this._replaceGuidsWithChannels(params));
}

View File

@ -17,6 +17,7 @@
import { EventEmitter } from 'events';
import { helper } from '../helper';
import { Channel } from './channels';
import { serializeError } from './serializers';
export class Dispatcher<Type, Initializer> extends EventEmitter implements Channel {
readonly _guid: string;
@ -43,16 +44,22 @@ export class Dispatcher<Type, Initializer> extends EventEmitter implements Chann
export class DispatcherScope {
readonly dispatchers = new Map<string, Dispatcher<any, any>>();
readonly dispatcherSymbol = Symbol('dispatcher');
sendMessageToClientTransport = (message: any) => {};
sendMessageToClientTransport = (message: string) => {};
async sendMessageToClient(guid: string, method: string, params: any): Promise<any> {
this.sendMessageToClientTransport({ guid, method, params: this._replaceDispatchersWithGuids(params) });
this.sendMessageToClientTransport(JSON.stringify({ guid, method, params: this._replaceDispatchersWithGuids(params) }));
}
async dispatchMessageFromClient(message: any): Promise<any> {
const dispatcher = this.dispatchers.get(message.guid)!;
const value = await (dispatcher as any)[message.method](this._replaceGuidsWithDispatchers(message.params));
return this._replaceDispatchersWithGuids(value);
async dispatchMessageFromClient(message: string) {
const parsedMessage = JSON.parse(message);
const { id, guid, method, params } = parsedMessage;
const dispatcher = this.dispatchers.get(guid)!;
try {
const result = await (dispatcher as any)[method](this._replaceGuidsWithDispatchers(params));
this.sendMessageToClientTransport(JSON.stringify({ id, result: this._replaceDispatchersWithGuids(result) }));
} catch (e) {
this.sendMessageToClientTransport(JSON.stringify({ id, error: serializeError(e) }));
}
}
private _replaceDispatchersWithGuids(payload: any): any {

64
src/rpc/serializers.ts Normal file
View File

@ -0,0 +1,64 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* 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 { TimeoutError } from '../errors';
import * as types from '../types';
export function serializeError(e: any): types.Error {
if (e instanceof Error)
return { message: e.message, stack: e.stack, name: e.name };
return { value: e };
}
export function parseError(error: types.Error): any {
if (error.message === undefined)
return error.value;
if (error.name === 'TimeoutError') {
const e = new TimeoutError(error.message);
e.stack = error.stack;
return e;
}
const e = new Error(error.message);
e.stack = error.stack;
return e;
}
export async function normalizeFilePayloads(files: string | types.FilePayload | string[] | types.FilePayload[]): Promise<types.FilePayload[]> {
let ff: string[] | types.FilePayload[];
if (!Array.isArray(files))
ff = [ files ] as string[] | types.FilePayload[];
else
ff = files;
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);
}
}
return filePayloads;
}

View File

@ -19,8 +19,8 @@ import * as js from '../../javascript';
import * as types from '../../types';
import { ElementHandleChannel, FrameChannel } from '../channels';
import { DispatcherScope } from '../dispatcher';
import { convertArg, FrameDispatcher } from './frameDispatcher';
import { JSHandleDispatcher } from './jsHandleDispatcher';
import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher';
import { FrameDispatcher } from './frameDispatcher';
export class ElementHandleDispatcher extends JSHandleDispatcher implements ElementHandleChannel {
readonly _elementHandle: ElementHandle;
@ -140,8 +140,8 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
return await this._elementHandle.boundingBox();
}
async screenshot(params: { options?: types.ElementScreenshotOptions }): Promise<Buffer> {
return await this._elementHandle.screenshot(params.options);
async screenshot(params: { options?: types.ElementScreenshotOptions }): Promise<string> {
return (await this._elementHandle.screenshot(params.options)).toString('base64');
}
async querySelector(params: { selector: string }): Promise<ElementHandleChannel | null> {
@ -154,11 +154,11 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
}
async $evalExpression(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._elementHandle._$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
return serializeResult(await this._elementHandle._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg)));
}
async $$evalExpression(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
return serializeResult(await this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg)));
}
}

View File

@ -16,10 +16,10 @@
import { Frame } from '../../frames';
import * as types from '../../types';
import { ElementHandleChannel, FrameChannel, JSHandleChannel, ResponseChannel, FrameInitializer } from '../channels';
import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { ElementHandleDispatcher, convertSelectOptionValues } from './elementHandlerDispatcher';
import { JSHandleDispatcher } from './jsHandleDispatcher';
import { convertSelectOptionValues, ElementHandleDispatcher } from './elementHandlerDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { ResponseDispatcher } from './networkDispatchers';
export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> implements FrameChannel {
@ -63,15 +63,15 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._frame._evaluateExpression(params.expression, params.isFunction, convertArg(this._scope, params.arg));
return serializeResult(await this._frame._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg)));
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel> {
return ElementHandleDispatcher.fromElement(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, convertArg(this._scope, params.arg)));
return ElementHandleDispatcher.fromElement(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg)));
}
async waitForSelector(params: { selector: string, options: types.WaitForElementOptions }): Promise<ElementHandleChannel | null> {
return ElementHandleDispatcher.fromNullableElement(this._scope, await this._frame.waitForSelector(params.selector));
return ElementHandleDispatcher.fromNullableElement(this._scope, await this._frame.waitForSelector(params.selector, params.options));
}
async dispatchEvent(params: { selector: string, type: string, eventInit: Object | undefined, options: types.TimeoutOptions }): Promise<void> {
@ -79,11 +79,11 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
}
async $eval(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._frame._$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
return serializeResult(await this._frame._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg)));
}
async $$eval(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, convertArg(this._scope, params.arg));
return serializeResult(await this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg)));
}
async querySelector(params: { selector: string }): Promise<ElementHandleChannel | null> {
@ -151,8 +151,8 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return this._frame.selectOption(params.selector, convertSelectOptionValues(params.values), params.options);
}
async setInputFiles(params: { selector: string, files: string | string[] | types.FilePayload | types.FilePayload[], options: types.NavigatingActionWaitOptions }): Promise<void> {
await this._frame.setInputFiles(params.selector, params.files, params.options);
async setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: string }[], options: types.NavigatingActionWaitOptions }): Promise<void> {
await this._frame.setInputFiles(params.selector, params.files.map(f => ({ name: f.name, mimeType: f.mimeType, buffer: Buffer.from(f.buffer, 'base64') })), params.options);
}
async type(params: { selector: string, text: string, options: { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } }): Promise<void> {
@ -172,26 +172,10 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
}
async waitForFunction(params: { expression: string, isFunction: boolean, arg: any; options: types.WaitForFunctionOptions }): Promise<JSHandleChannel> {
return ElementHandleDispatcher.from(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, convertArg(this._scope, params.arg), params.options));
return ElementHandleDispatcher.from(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params.options));
}
async title(): Promise<string> {
return await this._frame.title();
}
}
export function convertArg(scope: DispatcherScope, arg: any): any {
if (arg === null)
return null;
if (Array.isArray(arg))
return arg.map(item => convertArg(scope, item));
if (arg instanceof JSHandleDispatcher)
return arg._object;
if (typeof arg === 'object') {
const result: any = {};
for (const key of Object.keys(arg))
result[key] = convertArg(scope, arg[key]);
return result;
}
return arg;
}

View File

@ -17,8 +17,8 @@
import * as js from '../../javascript';
import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { convertArg } from './frameDispatcher';
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { parseEvaluationResultValue, serializeAsCallArgument } from '../../common/utilityScriptSerializers';
export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitializer> implements JSHandleChannel {
@ -29,11 +29,11 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<any> {
return this._object._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, convertArg(this._scope, params.arg));
return this._object._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg));
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<JSHandleChannel> {
const jsHandle = await this._object._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, convertArg(this._scope, params.arg));
const jsHandle = await this._object._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg));
return ElementHandleDispatcher.from(this._scope, jsHandle);
}
@ -53,3 +53,27 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
await this._object.dispose();
}
}
export function parseArgument(arg: { value: any, guids: JSHandleDispatcher[] }): any {
return convertDispatchersToObjects(parseEvaluationResultValue(arg.value, arg.guids));
}
export function serializeResult(arg: any): any {
return serializeAsCallArgument(arg, value => ({ fallThrough: value }));
}
function convertDispatchersToObjects(arg: any): any {
if (arg === null)
return null;
if (Array.isArray(arg))
return arg.map(item => convertDispatchersToObjects(item));
if (arg instanceof JSHandleDispatcher)
return arg._object;
if (typeof arg === 'object') {
const result: any = {};
for (const key of Object.keys(arg))
result[key] = convertDispatchersToObjects(arg[key]);
return result;
}
return arg;
}

View File

@ -17,12 +17,12 @@
import { BrowserContext } from '../../browserContext';
import { Events } from '../../events';
import { Frame } from '../../frames';
import { parseError, serializeError } from '../../helper';
import { Request } from '../../network';
import { Page } from '../../page';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel } from '../channels';
import { Dispatcher, DispatcherScope } from '../dispatcher';
import { parseError, serializeError } from '../serializers';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
import { DialogDispatcher } from './dialogDispatcher';
import { DownloadDispatcher } from './downloadDispatcher';
@ -129,8 +129,8 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
});
}
async screenshot(params: { options?: types.ScreenshotOptions }): Promise<Buffer> {
return await this._page.screenshot(params.options);
async screenshot(params: { options?: types.ScreenshotOptions }): Promise<string> {
return (await this._page.screenshot(params.options)).toString('base64');
}
async close(params: { options?: { runBeforeUnload?: boolean } }): Promise<void> {

View File

@ -308,6 +308,7 @@ export type ConsoleMessageLocation = {
export type Error = {
message?: string,
name?: string,
stack?: string,
value?: any
};