feat: auto-detect expression/function in js server (#5284)

This commit is contained in:
Pavel Feldman 2021-02-03 13:49:25 -08:00 committed by GitHub
parent fa1cf4108b
commit 3d253c4e5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 100 additions and 221 deletions

View File

@ -1261,11 +1261,11 @@ export type FrameNavigatedEvent = {
export type FrameEvalOnSelectorParams = { export type FrameEvalOnSelectorParams = {
selector: string, selector: string,
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type FrameEvalOnSelectorOptions = { export type FrameEvalOnSelectorOptions = {
isFunction?: boolean,
}; };
export type FrameEvalOnSelectorResult = { export type FrameEvalOnSelectorResult = {
value: SerializedValue, value: SerializedValue,
@ -1273,11 +1273,11 @@ export type FrameEvalOnSelectorResult = {
export type FrameEvalOnSelectorAllParams = { export type FrameEvalOnSelectorAllParams = {
selector: string, selector: string,
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type FrameEvalOnSelectorAllOptions = { export type FrameEvalOnSelectorAllOptions = {
isFunction?: boolean,
}; };
export type FrameEvalOnSelectorAllResult = { export type FrameEvalOnSelectorAllResult = {
value: SerializedValue, value: SerializedValue,
@ -1377,11 +1377,12 @@ export type FrameDispatchEventOptions = {
export type FrameDispatchEventResult = void; export type FrameDispatchEventResult = void;
export type FrameEvaluateExpressionParams = { export type FrameEvaluateExpressionParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
world?: 'main' | 'utility', world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionOptions = { export type FrameEvaluateExpressionOptions = {
isFunction?: boolean,
world?: 'main' | 'utility', world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionResult = { export type FrameEvaluateExpressionResult = {
@ -1389,11 +1390,12 @@ export type FrameEvaluateExpressionResult = {
}; };
export type FrameEvaluateExpressionHandleParams = { export type FrameEvaluateExpressionHandleParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
world?: 'main' | 'utility', world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionHandleOptions = { export type FrameEvaluateExpressionHandleOptions = {
isFunction?: boolean,
world?: 'main' | 'utility', world?: 'main' | 'utility',
}; };
export type FrameEvaluateExpressionHandleResult = { export type FrameEvaluateExpressionHandleResult = {
@ -1680,12 +1682,13 @@ export type FrameUncheckOptions = {
export type FrameUncheckResult = void; export type FrameUncheckResult = void;
export type FrameWaitForFunctionParams = { export type FrameWaitForFunctionParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
timeout?: number, timeout?: number,
pollingInterval?: number, pollingInterval?: number,
}; };
export type FrameWaitForFunctionOptions = { export type FrameWaitForFunctionOptions = {
isFunction?: boolean,
timeout?: number, timeout?: number,
pollingInterval?: number, pollingInterval?: number,
}; };
@ -1717,22 +1720,22 @@ export interface WorkerChannel extends Channel {
export type WorkerCloseEvent = {}; export type WorkerCloseEvent = {};
export type WorkerEvaluateExpressionParams = { export type WorkerEvaluateExpressionParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type WorkerEvaluateExpressionOptions = { export type WorkerEvaluateExpressionOptions = {
isFunction?: boolean,
}; };
export type WorkerEvaluateExpressionResult = { export type WorkerEvaluateExpressionResult = {
value: SerializedValue, value: SerializedValue,
}; };
export type WorkerEvaluateExpressionHandleParams = { export type WorkerEvaluateExpressionHandleParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type WorkerEvaluateExpressionHandleOptions = { export type WorkerEvaluateExpressionHandleOptions = {
isFunction?: boolean,
}; };
export type WorkerEvaluateExpressionHandleResult = { export type WorkerEvaluateExpressionHandleResult = {
handle: JSHandleChannel, handle: JSHandleChannel,
@ -1759,22 +1762,22 @@ export type JSHandleDisposeOptions = {};
export type JSHandleDisposeResult = void; export type JSHandleDisposeResult = void;
export type JSHandleEvaluateExpressionParams = { export type JSHandleEvaluateExpressionParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type JSHandleEvaluateExpressionOptions = { export type JSHandleEvaluateExpressionOptions = {
isFunction?: boolean,
}; };
export type JSHandleEvaluateExpressionResult = { export type JSHandleEvaluateExpressionResult = {
value: SerializedValue, value: SerializedValue,
}; };
export type JSHandleEvaluateExpressionHandleParams = { export type JSHandleEvaluateExpressionHandleParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type JSHandleEvaluateExpressionHandleOptions = { export type JSHandleEvaluateExpressionHandleOptions = {
isFunction?: boolean,
}; };
export type JSHandleEvaluateExpressionHandleResult = { export type JSHandleEvaluateExpressionHandleResult = {
handle: JSHandleChannel, handle: JSHandleChannel,
@ -1844,11 +1847,11 @@ export interface ElementHandleChannel extends JSHandleChannel {
export type ElementHandleEvalOnSelectorParams = { export type ElementHandleEvalOnSelectorParams = {
selector: string, selector: string,
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type ElementHandleEvalOnSelectorOptions = { export type ElementHandleEvalOnSelectorOptions = {
isFunction?: boolean,
}; };
export type ElementHandleEvalOnSelectorResult = { export type ElementHandleEvalOnSelectorResult = {
value: SerializedValue, value: SerializedValue,
@ -1856,11 +1859,11 @@ export type ElementHandleEvalOnSelectorResult = {
export type ElementHandleEvalOnSelectorAllParams = { export type ElementHandleEvalOnSelectorAllParams = {
selector: string, selector: string,
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type ElementHandleEvalOnSelectorAllOptions = { export type ElementHandleEvalOnSelectorAllOptions = {
isFunction?: boolean,
}; };
export type ElementHandleEvalOnSelectorAllResult = { export type ElementHandleEvalOnSelectorAllResult = {
value: SerializedValue, value: SerializedValue,
@ -2499,22 +2502,22 @@ export type ElectronApplicationWindowEvent = {
}; };
export type ElectronApplicationEvaluateExpressionParams = { export type ElectronApplicationEvaluateExpressionParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type ElectronApplicationEvaluateExpressionOptions = { export type ElectronApplicationEvaluateExpressionOptions = {
isFunction?: boolean,
}; };
export type ElectronApplicationEvaluateExpressionResult = { export type ElectronApplicationEvaluateExpressionResult = {
value: SerializedValue, value: SerializedValue,
}; };
export type ElectronApplicationEvaluateExpressionHandleParams = { export type ElectronApplicationEvaluateExpressionHandleParams = {
expression: string, expression: string,
isFunction: boolean, isFunction?: boolean,
arg: SerializedArgument, arg: SerializedArgument,
}; };
export type ElectronApplicationEvaluateExpressionHandleOptions = { export type ElectronApplicationEvaluateExpressionHandleOptions = {
isFunction?: boolean,
}; };
export type ElectronApplicationEvaluateExpressionHandleResult = { export type ElectronApplicationEvaluateExpressionHandleResult = {
handle: JSHandleChannel, handle: JSHandleChannel,

View File

@ -1050,7 +1050,7 @@ Frame:
parameters: parameters:
selector: string selector: string
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
value: SerializedValue value: SerializedValue
@ -1059,7 +1059,7 @@ Frame:
parameters: parameters:
selector: string selector: string
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
value: SerializedValue value: SerializedValue
@ -1149,7 +1149,7 @@ Frame:
evaluateExpression: evaluateExpression:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
world: world:
type: enum? type: enum?
@ -1162,7 +1162,7 @@ Frame:
evaluateExpressionHandle: evaluateExpressionHandle:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
world: world:
type: enum? type: enum?
@ -1396,7 +1396,7 @@ Frame:
waitForFunction: waitForFunction:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
timeout: number? timeout: number?
# When present, polls on interval. Otherwise, polls on raf. # When present, polls on interval. Otherwise, polls on raf.
@ -1458,7 +1458,7 @@ Worker:
evaluateExpression: evaluateExpression:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
value: SerializedValue value: SerializedValue
@ -1466,7 +1466,7 @@ Worker:
evaluateExpressionHandle: evaluateExpressionHandle:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
handle: JSHandle handle: JSHandle
@ -1489,7 +1489,7 @@ JSHandle:
evaluateExpression: evaluateExpression:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
value: SerializedValue value: SerializedValue
@ -1497,7 +1497,7 @@ JSHandle:
evaluateExpressionHandle: evaluateExpressionHandle:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
handle: JSHandle handle: JSHandle
@ -1541,7 +1541,7 @@ ElementHandle:
parameters: parameters:
selector: string selector: string
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
value: SerializedValue value: SerializedValue
@ -1550,7 +1550,7 @@ ElementHandle:
parameters: parameters:
selector: string selector: string
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
value: SerializedValue value: SerializedValue
@ -2113,7 +2113,7 @@ ElectronApplication:
evaluateExpression: evaluateExpression:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
value: SerializedValue value: SerializedValue
@ -2121,7 +2121,7 @@ ElectronApplication:
evaluateExpressionHandle: evaluateExpressionHandle:
parameters: parameters:
expression: string expression: string
isFunction: boolean isFunction: boolean?
arg: SerializedArgument arg: SerializedArgument
returns: returns:
handle: JSHandle handle: JSHandle

View File

@ -488,13 +488,13 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.FrameEvalOnSelectorParams = tObject({ scheme.FrameEvalOnSelectorParams = tObject({
selector: tString, selector: tString,
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.FrameEvalOnSelectorAllParams = tObject({ scheme.FrameEvalOnSelectorAllParams = tObject({
selector: tString, selector: tString,
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.FrameAddScriptTagParams = tObject({ scheme.FrameAddScriptTagParams = tObject({
@ -542,13 +542,13 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.FrameEvaluateExpressionParams = tObject({ scheme.FrameEvaluateExpressionParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
world: tOptional(tEnum(['main', 'utility'])), world: tOptional(tEnum(['main', 'utility'])),
}); });
scheme.FrameEvaluateExpressionHandleParams = tObject({ scheme.FrameEvaluateExpressionHandleParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
world: tOptional(tEnum(['main', 'utility'])), world: tOptional(tEnum(['main', 'utility'])),
}); });
@ -680,7 +680,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.FrameWaitForFunctionParams = tObject({ scheme.FrameWaitForFunctionParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
timeout: tOptional(tNumber), timeout: tOptional(tNumber),
pollingInterval: tOptional(tNumber), pollingInterval: tOptional(tNumber),
@ -692,25 +692,25 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.WorkerEvaluateExpressionParams = tObject({ scheme.WorkerEvaluateExpressionParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.WorkerEvaluateExpressionHandleParams = tObject({ scheme.WorkerEvaluateExpressionHandleParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.JSHandleDisposeParams = tOptional(tObject({})); scheme.JSHandleDisposeParams = tOptional(tObject({}));
scheme.ElementHandleDisposeParams = tType('JSHandleDisposeParams'); scheme.ElementHandleDisposeParams = tType('JSHandleDisposeParams');
scheme.JSHandleEvaluateExpressionParams = tObject({ scheme.JSHandleEvaluateExpressionParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.ElementHandleEvaluateExpressionParams = tType('JSHandleEvaluateExpressionParams'); scheme.ElementHandleEvaluateExpressionParams = tType('JSHandleEvaluateExpressionParams');
scheme.JSHandleEvaluateExpressionHandleParams = tObject({ scheme.JSHandleEvaluateExpressionHandleParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.ElementHandleEvaluateExpressionHandleParams = tType('JSHandleEvaluateExpressionHandleParams'); scheme.ElementHandleEvaluateExpressionHandleParams = tType('JSHandleEvaluateExpressionHandleParams');
@ -725,13 +725,13 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.ElementHandleEvalOnSelectorParams = tObject({ scheme.ElementHandleEvalOnSelectorParams = tObject({
selector: tString, selector: tString,
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.ElementHandleEvalOnSelectorAllParams = tObject({ scheme.ElementHandleEvalOnSelectorAllParams = tObject({
selector: tString, selector: tString,
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.ElementHandleBoundingBoxParams = tOptional(tObject({})); scheme.ElementHandleBoundingBoxParams = tOptional(tObject({}));
@ -923,12 +923,12 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.ElectronApplicationEvaluateExpressionParams = tObject({ scheme.ElectronApplicationEvaluateExpressionParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.ElectronApplicationEvaluateExpressionHandleParams = tObject({ scheme.ElectronApplicationEvaluateExpressionHandleParams = tObject({
expression: tString, expression: tString,
isFunction: tBoolean, isFunction: tOptional(tBoolean),
arg: tType('SerializedArgument'), arg: tType('SerializedArgument'),
}); });
scheme.ElectronApplicationCloseParams = tOptional(tObject({})); scheme.ElectronApplicationCloseParams = tOptional(tObject({}));

View File

@ -50,7 +50,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
}); });
} }
async evaluateExpressionInternal(expression: string, isFunction: boolean, ...args: any[]): Promise<any> { async evaluateExpressionInternal(expression: string, isFunction: boolean | undefined, ...args: any[]): Promise<any> {
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return js.evaluateExpression(this, true /* returnByValue */, expression, isFunction, ...args); return js.evaluateExpression(this, true /* returnByValue */, expression, isFunction, ...args);
}); });
@ -64,7 +64,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
}); });
} }
async evaluateExpressionHandleInternal(expression: string, isFunction: boolean, ...args: any[]): Promise<any> { async evaluateExpressionHandleInternal(expression: string, isFunction: boolean | undefined, ...args: any[]): Promise<any> {
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return js.evaluateExpression(this, false /* returnByValue */, expression, isFunction, ...args); return js.evaluateExpression(this, false /* returnByValue */, expression, isFunction, ...args);
}); });
@ -628,7 +628,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return this._page.selectors._queryAll(this._context.frame, selector, this, true /* adoptToMain */); return this._page.selectors._queryAll(this._context.frame, selector, this, true /* adoptToMain */);
} }
async _$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise<any> { async _$evalExpression(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
const handle = await this._page.selectors._query(this._context.frame, selector, this); const handle = await this._page.selectors._query(this._context.frame, selector, this);
if (!handle) if (!handle)
throw new Error(`Error: failed to find element matching selector "${selector}"`); throw new Error(`Error: failed to find element matching selector "${selector}"`);
@ -637,7 +637,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return result; return result;
} }
async _$$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise<any> { async _$$evalExpression(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
const arrayHandle = await this._page.selectors._queryArray(this._context.frame, selector, this); const arrayHandle = await this._page.selectors._queryArray(this._context.frame, selector, this);
const result = await arrayHandle._evaluateExpression(expression, isFunction, true, arg); const result = await arrayHandle._evaluateExpression(expression, isFunction, true, arg);
arrayHandle.dispose(); arrayHandle.dispose();

View File

@ -139,7 +139,7 @@ export class FrameManager {
await barrier.waitFor(); await barrier.waitFor();
this._signalBarriers.delete(barrier); this._signalBarriers.delete(barrier);
// Resolve in the next task, after all waitForNavigations. // Resolve in the next task, after all waitForNavigations.
await new Promise(makeWaitForNextTask()); await new Promise<void>(makeWaitForNextTask());
return result; return result;
} }
@ -579,7 +579,7 @@ export class Frame extends EventEmitter {
return this._context('utility'); return this._context('utility');
} }
async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any, world: types.World = 'main'): Promise<any> { async _evaluateExpressionHandle(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world); const context = await this._context(world);
const handle = await context.evaluateExpressionHandleInternal(expression, isFunction, arg); const handle = await context.evaluateExpressionHandleInternal(expression, isFunction, arg);
if (world === 'main') if (world === 'main')
@ -587,7 +587,7 @@ export class Frame extends EventEmitter {
return handle; return handle;
} }
async _evaluateExpression(expression: string, isFunction: boolean, arg: any, world: types.World = 'main'): Promise<any> { async _evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
const context = await this._context(world); const context = await this._context(world);
const value = await context.evaluateExpressionInternal(expression, isFunction, arg); const value = await context.evaluateExpressionInternal(expression, isFunction, arg);
if (world === 'main') if (world === 'main')
@ -632,7 +632,7 @@ export class Frame extends EventEmitter {
await this._page._doSlowMo(); await this._page._doSlowMo();
} }
async _$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise<any> { async _$evalExpression(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
const handle = await this.$(selector); const handle = await this.$(selector);
if (!handle) if (!handle)
throw new Error(`Error: failed to find element matching selector "${selector}"`); throw new Error(`Error: failed to find element matching selector "${selector}"`);
@ -641,7 +641,7 @@ export class Frame extends EventEmitter {
return result; return result;
} }
async _$$evalExpression(selector: string, expression: string, isFunction: boolean, arg: any): Promise<any> { async _$$evalExpression(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
const arrayHandle = await this._page.selectors._queryArray(this, selector); const arrayHandle = await this._page.selectors._queryArray(this, selector);
const result = await arrayHandle._evaluateExpression(expression, isFunction, true, arg); const result = await arrayHandle._evaluateExpression(expression, isFunction, true, arg);
arrayHandle.dispose(); arrayHandle.dispose();
@ -805,7 +805,7 @@ export class Frame extends EventEmitter {
let result: dom.ElementHandle; let result: dom.ElementHandle;
let error: Error | undefined; let error: Error | undefined;
let cspMessage: ConsoleMessage | undefined; let cspMessage: ConsoleMessage | undefined;
const actionPromise = new Promise<dom.ElementHandle>(async resolve => { const actionPromise = new Promise<void>(async resolve => {
try { try {
result = await func(); result = await func();
} catch (e) { } catch (e) {
@ -813,7 +813,7 @@ export class Frame extends EventEmitter {
} }
resolve(); resolve();
}); });
const errorPromise = new Promise(resolve => { const errorPromise = new Promise<void>(resolve => {
listeners.push(helper.addEventListener(this._page, Page.Events.Console, (message: ConsoleMessage) => { listeners.push(helper.addEventListener(this._page, Page.Events.Console, (message: ConsoleMessage) => {
if (message.type() === 'error' && message.text().includes('Content Security Policy')) { if (message.type() === 'error' && message.text().includes('Content Security Policy')) {
cspMessage = message; cspMessage = message;
@ -1016,7 +1016,7 @@ export class Frame extends EventEmitter {
}, this._page._timeoutSettings.timeout(options)); }, this._page._timeoutSettings.timeout(options));
} }
async _waitForFunctionExpression<R>(expression: string, isFunction: boolean, arg: any, options: types.WaitForFunctionOptions = {}): Promise<js.SmartHandle<R>> { async _waitForFunctionExpression<R>(expression: string, isFunction: boolean | undefined, arg: any, options: types.WaitForFunctionOptions = {}): Promise<js.SmartHandle<R>> {
if (typeof options.pollingInterval === 'number') if (typeof options.pollingInterval === 'number')
assert(options.pollingInterval > 0, 'Cannot poll with non-positive interval: ' + options.pollingInterval); assert(options.pollingInterval > 0, 'Cannot poll with non-positive interval: ' + options.pollingInterval);
const predicateBody = isFunction ? 'return (' + expression + ')(arg)' : 'return (' + expression + ')'; const predicateBody = isFunction ? 'return (' + expression + ')(arg)' : 'return (' + expression + ')';

View File

@ -17,17 +17,23 @@
import { serializeAsCallArgument, parseEvaluationResultValue } from '../common/utilityScriptSerializers'; import { serializeAsCallArgument, parseEvaluationResultValue } from '../common/utilityScriptSerializers';
export default class UtilityScript { export default class UtilityScript {
evaluate(returnByValue: boolean, expression: string) { evaluate(isFunction: boolean | undefined, returnByValue: boolean, expression: string, argCount: number, ...argsAndHandles: any[]) {
const result = global.eval(expression);
return returnByValue ? this._promiseAwareJsonValueNoThrow(result) : result;
}
callFunction(returnByValue: boolean, functionText: string, argCount: number, ...argsAndHandles: any[]) {
const args = argsAndHandles.slice(0, argCount); const args = argsAndHandles.slice(0, argCount);
const handles = argsAndHandles.slice(argCount); const handles = argsAndHandles.slice(argCount);
const parameters = args.map(a => parseEvaluationResultValue(a, handles)); const parameters = args.map(a => parseEvaluationResultValue(a, handles));
const func = global.eval('(' + functionText + ')'); expression = expression.trim();
const result = func(...parameters); if (expression.startsWith('function ') || expression.startsWith('async function '))
expression = '(' + expression + ')';
let result = global.eval(expression);
if (isFunction === true) {
result = result(...parameters);
} else if (isFunction === false) {
result = result;
} else {
// auto detect.
if (typeof result === 'function')
result = result(...parameters);
}
return returnByValue ? this._promiseAwareJsonValueNoThrow(result) : result; return returnByValue ? this._promiseAwareJsonValueNoThrow(result) : result;
} }

View File

@ -16,7 +16,6 @@
import * as dom from './dom'; import * as dom from './dom';
import * as utilityScriptSource from '../generated/utilityScriptSource'; import * as utilityScriptSource from '../generated/utilityScriptSource';
import * as sourceMap from '../utils/sourceMap';
import { serializeAsCallArgument } from './common/utilityScriptSerializers'; import { serializeAsCallArgument } from './common/utilityScriptSerializers';
import type UtilityScript from './injected/utilityScript'; import type UtilityScript from './injected/utilityScript';
@ -114,7 +113,7 @@ export class JSHandle<T = any> {
return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg); return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg);
} }
async _evaluateExpression(expression: string, isFunction: boolean, returnByValue: boolean, arg: any) { async _evaluateExpression(expression: string, isFunction: boolean | undefined, returnByValue: boolean, arg: any) {
const value = await evaluateExpression(this._context, returnByValue, expression, isFunction, this, arg); const value = await evaluateExpression(this._context, returnByValue, expression, isFunction, this, arg);
await this._context.doSlowMo(); await this._context.doSlowMo();
return value; return value;
@ -174,28 +173,25 @@ export async function evaluate(context: ExecutionContext, returnByValue: boolean
return evaluateExpression(context, returnByValue, String(pageFunction), typeof pageFunction === 'function', ...args); return evaluateExpression(context, returnByValue, String(pageFunction), typeof pageFunction === 'function', ...args);
} }
export async function evaluateExpression(context: ExecutionContext, returnByValue: boolean, expression: string, isFunction: boolean, ...args: any[]): Promise<any> { export async function evaluateExpression(context: ExecutionContext, returnByValue: boolean, expression: string, isFunction: boolean | undefined, ...args: any[]): Promise<any> {
const utilityScript = await context.utilityScript(); const utilityScript = await context.utilityScript();
if (!isFunction) {
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
return context._delegate.evaluateWithArguments(script, returnByValue, utilityScript, [returnByValue, expression], []);
}
let functionText = expression; if (isFunction) {
try {
new Function('(' + functionText + ')');
} catch (e1) {
// This means we might have a function shorthand. Try another
// time prefixing 'function '.
if (functionText.startsWith('async '))
functionText = 'async function ' + functionText.substring('async '.length);
else
functionText = 'function ' + functionText;
try { try {
new Function('(' + functionText + ')'); new Function('(' + expression + ')');
} catch (e2) { } catch (e1) {
// We tried hard to serialize, but there's a weird beast here. // This means we might have a function shorthand. Try another
throw new Error('Passed function is not well-serializable!'); // time prefixing 'function '.
if (expression.startsWith('async '))
expression = 'async function ' + expression.substring('async '.length);
else
expression = 'function ' + expression;
try {
new Function('(' + expression + ')');
} catch (e2) {
// We tried hard to serialize, but there's a weird beast here.
throw new Error('Passed function is not well-serializable!');
}
} }
} }
@ -228,11 +224,10 @@ export async function evaluateExpression(context: ExecutionContext, returnByValu
utilityScriptObjectIds.push(handle._objectId!); utilityScriptObjectIds.push(handle._objectId!);
} }
functionText += await sourceMap.generateSourceMapUrl(expression, functionText);
// See UtilityScript for arguments. // See UtilityScript for arguments.
const utilityScriptValues = [returnByValue, functionText, args.length, ...args]; const utilityScriptValues = [isFunction, returnByValue, expression, args.length, ...args];
const script = `(utilityScript, ...args) => utilityScript.callFunction(...args)`; const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
try { try {
return await context._delegate.evaluateWithArguments(script, returnByValue, utilityScript, utilityScriptValues, utilityScriptObjectIds); return await context._delegate.evaluateWithArguments(script, returnByValue, utilityScript, utilityScriptValues, utilityScriptObjectIds);
} finally { } finally {

View File

@ -503,7 +503,7 @@ export class Worker extends EventEmitter {
private _url: string; private _url: string;
private _executionContextPromise: Promise<js.ExecutionContext>; private _executionContextPromise: Promise<js.ExecutionContext>;
private _executionContextCallback: (value?: js.ExecutionContext) => void; private _executionContextCallback: (value: js.ExecutionContext) => void;
_existingExecutionContext: js.ExecutionContext | null = null; _existingExecutionContext: js.ExecutionContext | null = null;
constructor(url: string) { constructor(url: string) {
@ -522,11 +522,11 @@ export class Worker extends EventEmitter {
return this._url; return this._url;
} }
async _evaluateExpression(expression: string, isFunction: boolean, arg: any): Promise<any> { async _evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
return js.evaluateExpression(await this._executionContextPromise, true /* returnByValue */, expression, isFunction, arg); return js.evaluateExpression(await this._executionContextPromise, true /* returnByValue */, expression, isFunction, arg);
} }
async _evaluateExpressionHandle(expression: string, isFunction: boolean, arg: any): Promise<any> { async _evaluateExpressionHandle(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
return js.evaluateExpression(await this._executionContextPromise, false /* returnByValue */, expression, isFunction, arg); return js.evaluateExpression(await this._executionContextPromise, false /* returnByValue */, expression, isFunction, arg);
} }
} }

View File

@ -1,125 +0,0 @@
/**
* 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 util from 'util';
import { getCallerFilePath } from './stackTrace';
import { isDebugMode } from './utils';
type Position = {
line: number;
column: number;
};
export async function generateSourceMapUrl(functionText: string, generatedText: string): Promise<string> {
if (!isDebugMode())
return '';
const sourceMapUrl = await innerGenerateSourceMapUrl(functionText, generatedText);
return sourceMapUrl || '';
}
async function innerGenerateSourceMapUrl(functionText: string, generatedText: string): Promise<string | undefined> {
const filePath = getCallerFilePath();
if (!filePath)
return;
try {
const generatedIndex = generatedText.indexOf(functionText);
if (generatedIndex === -1)
return;
const compiledPosition = findPosition(generatedText, generatedIndex);
const source = await util.promisify(fs.readFile)(filePath, 'utf8');
const sourceIndex = source.indexOf(functionText);
if (sourceIndex === -1)
return;
const sourcePosition = findPosition(source, sourceIndex);
const delta = findPosition(functionText, functionText.length);
const sourceMap = generateSourceMap(filePath, sourcePosition, compiledPosition, delta);
return `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(sourceMap).toString('base64')}\n`;
} catch (e) {
}
}
const VLQ_BASE_SHIFT = 5;
const VLQ_BASE = 1 << VLQ_BASE_SHIFT;
const VLQ_BASE_MASK = VLQ_BASE - 1;
const VLQ_CONTINUATION_BIT = VLQ_BASE;
const BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function base64VLQ(value: number): string {
if (value < 0)
value = ((-value) << 1) | 1;
else
value <<= 1;
let result = '';
do {
let digit = value & VLQ_BASE_MASK;
value >>>= VLQ_BASE_SHIFT;
if (value > 0)
digit |= VLQ_CONTINUATION_BIT;
result += BASE64_DIGITS[digit];
} while (value > 0);
return result;
}
function generateSourceMap(filePath: string, sourcePosition: Position, compiledPosition: Position, delta: Position): any {
const mappings = [];
let lastCompiled = { line: 0, column: 0 };
let lastSource = { line: 0, column: 0 };
for (let line = 0; line < delta.line; line++) {
// We need at least a mapping per line. This will yield an execution line at the start of each line.
// Note: for more granular mapping, we can do word-by-word.
const source = advancePosition(sourcePosition, { line, column: 0 });
const compiled = advancePosition(compiledPosition, { line, column: 0 });
while (lastCompiled.line < compiled.line) {
mappings.push(';');
lastCompiled.line++;
lastCompiled.column = 0;
}
mappings.push(base64VLQ(compiled.column - lastCompiled.column));
mappings.push(base64VLQ(0)); // Source index.
mappings.push(base64VLQ(source.line - lastSource.line));
mappings.push(base64VLQ(source.column - lastSource.column));
lastCompiled = compiled;
lastSource = source;
}
return JSON.stringify({
version: 3,
sources: ['file://' + filePath],
names: [],
mappings: mappings.join(''),
});
}
function findPosition(source: string, offset: number): Position {
const result: Position = { line: 0, column: 0 };
let index = 0;
while (true) {
const newline = source.indexOf('\n', index);
if (newline === -1 || newline >= offset)
break;
result.line++;
index = newline + 1;
}
result.column = offset - index;
return result;
}
function advancePosition(position: Position, delta: Position): Position {
return {
line: position.line + delta.line,
column: delta.column + (delta.line ? 0 : position.column),
};
}