chore: explicitly type SerializedArgument, fix rpc dispatchEvent (#2988)

We now have types for SerializedValue/SerializedArgument. This will
allow us to avoid double parse/serialize for evaluation arguments/results.

Drive-by: typing exposed a bug in ElementHandle.dispatchEvent().
This commit is contained in:
Dmitry Gozman 2020-07-17 09:53:13 -07:00 committed by GitHub
parent 070a257600
commit d8bedd851d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 114 deletions

View file

@ -14,6 +14,15 @@
* limitations under the License.
*/
export type SerializedValue =
undefined | boolean | number | string |
{ v: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0' } |
{ d: string } |
{ r: [string, string] } |
{ a: SerializedValue[] } |
{ o: { [key: string]: SerializedValue } } |
{ h: number };
function isRegExp(obj: any): obj is RegExp {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
}
@ -26,44 +35,48 @@ function isError(obj: any): obj is Error {
return obj instanceof Error || (obj && obj.__proto__ && obj.__proto__.name === 'Error');
}
export function parseEvaluationResultValue(value: any, handles: any[] = []): any {
export function parseEvaluationResultValue(value: SerializedValue, handles: any[] = []): any {
if (value === undefined)
return undefined;
if (typeof value === 'object') {
if (value.v === 'undefined')
return undefined;
if (value.v === null)
return null;
if (value.v === 'NaN')
return NaN;
if (value.v === 'Infinity')
return Infinity;
if (value.v === '-Infinity')
return -Infinity;
if (value.v === '-0')
return -0;
if (value.d)
return new Date(value.d);
if (value.r)
return new RegExp(value.r[0], value.r[1]);
if (value.a)
return value.a.map((a: any) => parseEvaluationResultValue(a, handles));
if (value.o) {
for (const name of Object.keys(value.o))
value.o[name] = parseEvaluationResultValue(value.o[name], handles);
return value.o;
if ('v' in value) {
if (value.v === 'undefined')
return undefined;
if (value.v === 'null')
return null;
if (value.v === 'NaN')
return NaN;
if (value.v === 'Infinity')
return Infinity;
if (value.v === '-Infinity')
return -Infinity;
if (value.v === '-0')
return -0;
}
if (typeof value.h === 'number')
if ('d' in value)
return new Date(value.d);
if ('r' in value)
return new RegExp(value.r[0], value.r[1]);
if ('a' in value)
return value.a.map((a: any) => parseEvaluationResultValue(a, handles));
if ('o' in value) {
const result: any = {};
for (const name of Object.keys(value.o))
result[name] = parseEvaluationResultValue(value.o[name], handles);
return result;
}
if ('h' in value)
return handles[value.h];
}
return value;
}
export function serializeAsCallArgument(value: any, jsHandleSerializer: (value: any) => { fallThrough?: any }): any {
export type HandleOrValue = { h: number } | { fallThrough: any };
export function serializeAsCallArgument(value: any, jsHandleSerializer: (value: any) => HandleOrValue): SerializedValue {
return serialize(value, jsHandleSerializer, new Set());
}
function serialize(value: any, jsHandleSerializer: (value: any) => { fallThrough?: any }, visited: Set<any>): any {
function serialize(value: any, jsHandleSerializer: (value: any) => HandleOrValue, visited: Set<any>): SerializedValue {
const result = jsHandleSerializer(value);
if ('fallThrough' in result)
value = result.fallThrough;
@ -77,7 +90,7 @@ function serialize(value: any, jsHandleSerializer: (value: any) => { fallThrough
if (Object.is(value, undefined))
return { v: 'undefined' };
if (Object.is(value, null))
return { v: null };
return { v: 'null' };
if (Object.is(value, NaN))
return { v: 'NaN' };
if (Object.is(value, Infinity))
@ -86,7 +99,12 @@ function serialize(value: any, jsHandleSerializer: (value: any) => { fallThrough
return { v: '-Infinity' };
if (Object.is(value, -0))
return { v: '-0' };
if (isPrimitiveValue(value))
if (typeof value === 'boolean')
return value;
if (typeof value === 'number')
return value;
if (typeof value === 'string')
return value;
if (isError(value)) {
@ -130,14 +148,3 @@ function serialize(value: any, jsHandleSerializer: (value: any) => { fallThrough
return { o: result };
}
}
export function isPrimitiveValue(value: any): boolean {
switch (typeof value) {
case 'boolean':
case 'number':
case 'string':
return true;
default:
return false;
}
}

View file

@ -196,7 +196,7 @@ export async function evaluateExpression(context: ExecutionContext, returnByValu
return handles.length - 1;
};
args = args.map(arg => serializeAsCallArgument(arg, (handle: any): { h?: number, fallThrough?: any } => {
args = args.map(arg => serializeAsCallArgument(arg, handle => {
if (handle instanceof JSHandle) {
if (!handle._objectId)
return { fallThrough: handle._value };

View file

@ -16,8 +16,11 @@
import { EventEmitter } from 'events';
import * as types from '../types';
import { SerializedValue } from '../common/utilityScriptSerializers';
export type Binary = string;
export type SerializedArgument = { value: SerializedValue, handles: Channel[] };
export type BrowserContextOptions = {
viewport?: types.Size | null,
ignoreHTTPSErrors?: boolean,
@ -215,17 +218,17 @@ export interface FrameChannel extends Channel {
on(event: 'loadstate', callback: (params: { add?: types.LifecycleEvent, remove?: types.LifecycleEvent }) => void): this;
on(event: 'navigated', callback: (params: FrameNavigatedEvent) => void): this;
evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
addScriptTag(params: { url?: string, content?: string, type?: string }): Promise<{ element: ElementHandleChannel }>;
addStyleTag(params: { url?: string, content?: string }): Promise<{ element: ElementHandleChannel }>;
check(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions): Promise<void>;
content(): Promise<{ value: string }>;
dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions): Promise<void>;
dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise<void>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ handle: JSHandleChannel }>;
dispatchEvent(params: { selector: string, type: string, eventInit: SerializedArgument } & types.TimeoutOptions): Promise<void>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }>;
fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions): Promise<void>;
focus(params: { selector: string } & types.TimeoutOptions): Promise<void>;
frameElement(): Promise<{ element: ElementHandleChannel }>;
@ -244,7 +247,7 @@ export interface FrameChannel extends Channel {
title(): Promise<{ value: string }>;
type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }>;
waitForFunction(params: { expression: string, isFunction: boolean, arg: SerializedArgument } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }>;
waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }>;
}
export type FrameInitializer = {
@ -256,8 +259,8 @@ export type FrameInitializer = {
export interface WorkerChannel extends Channel {
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ handle: JSHandleChannel }>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }>;
}
export type WorkerInitializer = {
url: string,
@ -268,11 +271,11 @@ export interface JSHandleChannel extends Channel {
on(event: 'previewUpdated', callback: (params: { preview: string }) => void): this;
dispose(): Promise<void>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ handle: JSHandleChannel }>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }>;
getPropertyList(): Promise<{ properties: { name: string, value: JSHandleChannel}[] }>;
getProperty(params: { name: string }): Promise<{ handle: JSHandleChannel }>;
jsonValue(): Promise<{ value: any }>;
jsonValue(): Promise<{ value: SerializedValue }>;
}
export type JSHandleInitializer = {
preview: string,
@ -280,14 +283,14 @@ export type JSHandleInitializer = {
export interface ElementHandleChannel extends JSHandleChannel {
evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }>;
evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }>;
evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
boundingBox(): Promise<{ value: types.Rect | null }>;
check(params: { force?: boolean } & { noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
click(params: { force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions): Promise<void>;
contentFrame(): Promise<{ frame: FrameChannel | null }>;
dblclick(params: { force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions): Promise<void>;
dispatchEvent(params: { type: string, eventInit: any }): Promise<void>;
dispatchEvent(params: { type: string, eventInit: SerializedArgument }): Promise<void>;
fill(params: { value: string } & types.NavigatingActionWaitOptions): Promise<void>;
focus(): Promise<void>;
getAttribute(params: { name: string }): Promise<{ value: string | null }>;
@ -359,11 +362,12 @@ export type ConsoleMessageInitializer = {
export interface BindingCallChannel extends Channel {
reject(params: { error: types.Error }): void;
resolve(params: { result: any }): void;
resolve(params: { result: SerializedArgument }): void;
}
export type BindingCallInitializer = {
frame: FrameChannel,
name: string,
// TODO: migrate this to SerializedArgument.
args: any[]
};
@ -443,9 +447,9 @@ export interface ElectronApplicationChannel extends Channel {
on(event: 'close', callback: () => void): this;
on(event: 'window', callback: (params: { page: PageChannel, browserWindow: JSHandleChannel }) => void): this;
newBrowserWindow(params: { arg: any }): Promise<{ page: PageChannel }>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ handle: JSHandleChannel }>;
newBrowserWindow(params: { arg: SerializedArgument }): Promise<{ page: PageChannel }>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }>;
close(): Promise<void>;
}
export type ElectronApplicationInitializer = {

View file

@ -80,7 +80,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
async dispatchEvent(type: string, eventInit: Object = {}) {
return this._wrapApiCall('elementHandle.dispatchEvent', async () => {
await this._elementChannel.dispatchEvent({ type, eventInit });
await this._elementChannel.dispatchEvent({ type, eventInit: serializeArgument(eventInit) });
});
}

View file

@ -14,10 +14,10 @@
* limitations under the License.
*/
import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { JSHandleChannel, JSHandleInitializer, SerializedArgument, Channel } from '../channels';
import { ElementHandle } from './elementHandle';
import { ChannelOwner } from './channelOwner';
import { serializeAsCallArgument, parseEvaluationResultValue } from '../../common/utilityScriptSerializers';
import { serializeAsCallArgument, parseEvaluationResultValue, SerializedValue } from '../../common/utilityScriptSerializers';
type NoHandles<Arg> = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles<Arg[Key]> } : Arg);
type Unboxed<Arg> =
@ -95,20 +95,22 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleIni
}
}
export function serializeArgument(arg: any): any {
const guids: { guid: string }[] = [];
const pushHandle = (guid: string): number => {
guids.push({ guid });
return guids.length - 1;
// This function takes care of converting all JSHandles to their channels,
// so that generic channel serializer converts them to guids.
export function serializeArgument(arg: any): SerializedArgument {
const handles: Channel[] = [];
const pushHandle = (channel: Channel): number => {
handles.push(channel);
return handles.length - 1;
};
const value = serializeAsCallArgument(arg, value => {
if (value instanceof ChannelOwner)
return { h: pushHandle(value._guid) };
if (value instanceof JSHandle)
return { h: pushHandle(value._channel) };
return { fallThrough: value };
});
return { value, guids };
return { value, handles };
}
export function parseResult(arg: any): any {
export function parseResult(arg: SerializedValue): any {
return parseEvaluationResultValue(arg, []);
}

View file

@ -16,12 +16,13 @@
import { Dispatcher, DispatcherScope, lookupDispatcher } from './dispatcher';
import { Electron, ElectronApplication, ElectronEvents, ElectronPage } from '../../server/electron';
import { ElectronApplicationChannel, ElectronApplicationInitializer, PageChannel, JSHandleChannel, ElectronInitializer, ElectronChannel, ElectronLaunchOptions } from '../channels';
import { ElectronApplicationChannel, ElectronApplicationInitializer, PageChannel, JSHandleChannel, ElectronInitializer, ElectronChannel, ElectronLaunchOptions, SerializedArgument } from '../channels';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { BrowserContextBase } from '../../browserContext';
import { PageDispatcher } from './pageDispatcher';
import { parseArgument } from './jsHandleDispatcher';
import { createHandle } from './elementHandlerDispatcher';
import { SerializedValue } from '../../common/utilityScriptSerializers';
export class ElectronDispatcher extends Dispatcher<Electron, ElectronInitializer> implements ElectronChannel {
constructor(scope: DispatcherScope, electron: Electron) {
@ -49,17 +50,17 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
});
}
async newBrowserWindow(params: { arg: any }): Promise<{ page: PageChannel }> {
async newBrowserWindow(params: { arg: SerializedArgument }): Promise<{ page: PageChannel }> {
const page = await this._object.newBrowserWindow(parseArgument(params.arg));
return { page: lookupDispatcher<PageChannel>(page) };
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
const handle = this._object._nodeElectronHandle!;
return { value: await handle._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg)) };
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ handle: JSHandleChannel }> {
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }> {
const handle = this._object._nodeElectronHandle!;
const result = await handle._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg));
return { handle: createHandle(this._scope, result) };

View file

@ -17,10 +17,11 @@
import { ElementHandle } from '../../dom';
import * as js from '../../javascript';
import * as types from '../../types';
import { ElementHandleChannel, FrameChannel, Binary } from '../channels';
import { ElementHandleChannel, FrameChannel, Binary, SerializedArgument } from '../channels';
import { DispatcherScope, lookupNullableDispatcher } from './dispatcher';
import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDispatcher';
import { FrameDispatcher } from './frameDispatcher';
import { SerializedValue } from '../../common/utilityScriptSerializers';
export function createHandle(scope: DispatcherScope, handle: js.JSHandle): JSHandleDispatcher {
return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()!) : new JSHandleDispatcher(scope, handle);
@ -64,8 +65,8 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
return { value: await this._elementHandle.innerHTML() };
}
async dispatchEvent(params: { type: string, eventInit: Object }) {
await this._elementHandle.dispatchEvent(params.type, params.eventInit);
async dispatchEvent(params: { type: string, eventInit: SerializedArgument }) {
await this._elementHandle.dispatchEvent(params.type, parseArgument(params.eventInit));
}
async scrollIntoViewIfNeeded(params: types.TimeoutOptions) {
@ -138,11 +139,11 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme
return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) };
}
async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._elementHandle._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
}
async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._elementHandle._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
}
}

View file

@ -16,11 +16,12 @@
import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent, kNavigationEvent, NavigationEvent } from '../../frames';
import * as types from '../../types';
import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel } from '../channels';
import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel, SerializedArgument } from '../channels';
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
import { convertSelectOptionValues, ElementHandleDispatcher, createHandle, convertInputFiles } from './elementHandlerDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher';
import { ResponseDispatcher, RequestDispatcher } from './networkDispatchers';
import { SerializedValue } from '../../common/utilityScriptSerializers';
export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> implements FrameChannel {
private _frame: Frame;
@ -64,11 +65,11 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return { element: new ElementHandleDispatcher(this._scope, await this._frame.frameElement()) };
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._frame._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) };
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ handle: JSHandleChannel }> {
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }> {
return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) };
}
@ -76,15 +77,15 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(params.selector, params)) };
}
async dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise<void> {
async dispatchEvent(params: { selector: string, type: string, eventInit: SerializedArgument } & types.TimeoutOptions): Promise<void> {
return this._frame.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params);
}
async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._frame._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
}
async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
}
@ -173,7 +174,7 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
await this._frame.uncheck(params.selector, params);
}
async waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }> {
async waitForFunction(params: { expression: string, isFunction: boolean, arg: SerializedArgument } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }> {
return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) };
}

View file

@ -15,9 +15,9 @@
*/
import * as js from '../../javascript';
import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { JSHandleChannel, JSHandleInitializer, SerializedArgument } from '../channels';
import { Dispatcher, DispatcherScope } from './dispatcher';
import { parseEvaluationResultValue, serializeAsCallArgument } from '../../common/utilityScriptSerializers';
import { parseEvaluationResultValue, serializeAsCallArgument, SerializedValue } from '../../common/utilityScriptSerializers';
import { createHandle } from './elementHandlerDispatcher';
export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitializer> implements JSHandleChannel {
@ -29,11 +29,11 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
jsHandle._setPreviewCallback(preview => this._dispatchEvent('previewUpdated', { preview }));
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._object._evaluateExpression(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg))) };
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ handle: JSHandleChannel }> {
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument}): Promise<{ handle: JSHandleChannel }> {
const jsHandle = await this._object._evaluateExpression(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg));
return { handle: createHandle(this._scope, jsHandle) };
}
@ -51,7 +51,7 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
return { properties };
}
async jsonValue(): Promise<{ value: any }> {
async jsonValue(): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._object.jsonValue()) };
}
@ -60,26 +60,12 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, JSHandleInitiali
}
}
export function parseArgument(arg: { value: any, guids: JSHandleDispatcher[] }): any {
return parseEvaluationResultValue(arg.value, convertDispatchersToObjects(arg.guids));
// Generic channel parser converts guids to JSHandleDispatchers,
// and this function takes care of coverting them into underlying JSHandles.
export function parseArgument(arg: SerializedArgument): any {
return parseEvaluationResultValue(arg.value, arg.handles.map(arg => (arg as JSHandleDispatcher)._object));
}
export function serializeResult(arg: any): any {
export function serializeResult(arg: any): SerializedValue {
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

@ -20,7 +20,7 @@ import { Frame } from '../../frames';
import { Request } from '../../network';
import { Page, Worker } from '../../page';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, PDFOptions } from '../channels';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, PDFOptions, SerializedArgument } from '../channels';
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
import { parseError, serializeError, headersArrayToObject } from '../serializers';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
@ -32,6 +32,7 @@ import { serializeResult, parseArgument } from './jsHandleDispatcher';
import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher';
import { FileChooser } from '../../fileChooser';
import { CRCoverage } from '../../chromium/crCoverage';
import { SerializedValue } from '../../common/utilityScriptSerializers';
export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements PageChannel {
private _page: Page;
@ -232,11 +233,11 @@ export class WorkerDispatcher extends Dispatcher<Worker, WorkerInitializer> impl
worker.on(Events.Worker.Close, () => this._dispatchEvent('close'));
}
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<{ value: any }> {
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ value: SerializedValue }> {
return { value: serializeResult(await this._object._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) };
}
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any, isPage?: boolean }): Promise<{ handle: JSHandleChannel }> {
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: SerializedArgument }): Promise<{ handle: JSHandleChannel }> {
return { handle: createHandle(this._scope, await this._object._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) };
}
}
@ -262,7 +263,7 @@ export class BindingCallDispatcher extends Dispatcher<{}, BindingCallInitializer
return this._promise;
}
resolve(params: { result: any }) {
resolve(params: { result: SerializedArgument }) {
this._resolve!(parseArgument(params.result));
}

View file

@ -132,6 +132,20 @@ describe('Page.dispatchEvent(drag)', function() {
});
});
describe('ElementHandle.dispatchEvent(drag)', function() {
it.fail(WEBKIT)('should dispatch drag drop events', async({page, server}) => {
await page.goto(server.PREFIX + '/drag-n-drop.html');
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
const source = await page.$('#source');
await source.dispatchEvent('dragstart', { dataTransfer });
const target = await page.$('#target');
await target.dispatchEvent('drop', { dataTransfer });
expect(await page.evaluate(() => {
return source.parentElement === target;
})).toBeTruthy();
});
});
describe('ElementHandle.dispatchEvent(click)', function() {
it('should dispatch click event', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');