mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-28 06:07:53 +03:00
chore(rpc): serialize rpc into actual wire string (#2740)
This commit is contained in:
parent
3e33523ee3
commit
4e94bdabfd
26
src/dom.ts
26
src/dom.ts
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -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 }));
|
||||
}
|
||||
}
|
||||
|
@ -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) }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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;
|
||||
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 };
|
||||
}
|
||||
return arg;
|
||||
|
||||
export function parseResult(arg: any): any {
|
||||
return parseEvaluationResultValue(arg, []);
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
64
src/rpc/serializers.ts
Normal 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;
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -308,6 +308,7 @@ export type ConsoleMessageLocation = {
|
||||
|
||||
export type Error = {
|
||||
message?: string,
|
||||
name?: string,
|
||||
stack?: string,
|
||||
value?: any
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user