diff --git a/src/common/utilityScriptSerializers.ts b/src/common/utilityScriptSerializers.ts index 8aba6ec70d..31d98c8477 100644 --- a/src/common/utilityScriptSerializers.ts +++ b/src/common/utilityScriptSerializers.ts @@ -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 { +function serialize(value: any, jsHandleSerializer: (value: any) => HandleOrValue, visited: Set): 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; - } -} diff --git a/src/javascript.ts b/src/javascript.ts index 667abae6d3..6a0154a185 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -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 }; diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index ede604e07a..b1e9951973 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -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; click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions): Promise; content(): Promise<{ value: string }>; dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions): Promise; - dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise; - 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; + 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; focus(params: { selector: string } & types.TimeoutOptions): Promise; 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; uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; - 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; - 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; click(params: { force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions): Promise; contentFrame(): Promise<{ frame: FrameChannel | null }>; dblclick(params: { force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions): Promise; - dispatchEvent(params: { type: string, eventInit: any }): Promise; + dispatchEvent(params: { type: string, eventInit: SerializedArgument }): Promise; fill(params: { value: string } & types.NavigatingActionWaitOptions): Promise; focus(): Promise; 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; } export type ElectronApplicationInitializer = { diff --git a/src/rpc/client/elementHandle.ts b/src/rpc/client/elementHandle.ts index 54cb34dee1..0f2cb5ce54 100644 --- a/src/rpc/client/elementHandle.ts +++ b/src/rpc/client/elementHandle.ts @@ -80,7 +80,7 @@ export class ElementHandle extends JSHandle { 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) }); }); } diff --git a/src/rpc/client/jsHandle.ts b/src/rpc/client/jsHandle.ts index ee45d2741d..ec1a133eaa 100644 --- a/src/rpc/client/jsHandle.ts +++ b/src/rpc/client/jsHandle.ts @@ -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 extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles } : Arg); type Unboxed = @@ -95,20 +95,22 @@ export class JSHandle extends ChannelOwner { - 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, []); } diff --git a/src/rpc/server/electronDispatcher.ts b/src/rpc/server/electronDispatcher.ts index 1166f11a0f..6baa174e04 100644 --- a/src/rpc/server/electronDispatcher.ts +++ b/src/rpc/server/electronDispatcher.ts @@ -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 implements ElectronChannel { constructor(scope: DispatcherScope, electron: Electron) { @@ -49,17 +50,17 @@ export class ElectronApplicationDispatcher extends Dispatcher { + async newBrowserWindow(params: { arg: SerializedArgument }): Promise<{ page: PageChannel }> { const page = await this._object.newBrowserWindow(parseArgument(params.arg)); return { page: lookupDispatcher(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) }; diff --git a/src/rpc/server/elementHandlerDispatcher.ts b/src/rpc/server/elementHandlerDispatcher.ts index 7e4454c394..383dfdd9fb 100644 --- a/src/rpc/server/elementHandlerDispatcher.ts +++ b/src/rpc/server/elementHandlerDispatcher.ts @@ -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))) }; } } diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index 30197edbe3..526d986519 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -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 implements FrameChannel { private _frame: Frame; @@ -64,11 +65,11 @@ export class FrameDispatcher extends Dispatcher 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 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 { + async dispatchEvent(params: { selector: string, type: string, eventInit: SerializedArgument } & types.TimeoutOptions): Promise { 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 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)) }; } diff --git a/src/rpc/server/jsHandleDispatcher.ts b/src/rpc/server/jsHandleDispatcher.ts index 4737bc6a9a..f63f40c8b9 100644 --- a/src/rpc/server/jsHandleDispatcher.ts +++ b/src/rpc/server/jsHandleDispatcher.ts @@ -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 implements JSHandleChannel { @@ -29,11 +29,11 @@ export class JSHandleDispatcher extends Dispatcher 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 { + async jsonValue(): Promise<{ value: SerializedValue }> { return { value: serializeResult(await this._object.jsonValue()) }; } @@ -60,26 +60,12 @@ export class JSHandleDispatcher extends Dispatcher (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; -} diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index b670b4a145..ce31b34b74 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -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 implements PageChannel { private _page: Page; @@ -232,11 +233,11 @@ export class WorkerDispatcher extends Dispatcher 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)); } diff --git a/test/dispatchevent.jest.js b/test/dispatchevent.jest.js index 67be46e7c0..5bc5d419ba 100644 --- a/test/dispatchevent.jest.js +++ b/test/dispatchevent.jest.js @@ -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');