mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore(webkit): use async/await to make eval more readable (#789)
This commit is contained in:
parent
0f305e05e9
commit
b8199c0813
@ -25,15 +25,14 @@ export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__';
|
|||||||
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
||||||
|
|
||||||
export class WKExecutionContext implements js.ExecutionContextDelegate {
|
export class WKExecutionContext implements js.ExecutionContextDelegate {
|
||||||
private _globalObjectId?: Promise<string>;
|
private _globalObjectIdPromise?: Promise<Protocol.Runtime.RemoteObjectId>;
|
||||||
_session: WKSession;
|
private readonly _session: WKSession;
|
||||||
_contextId: number | undefined;
|
readonly _contextId: number | undefined;
|
||||||
private _contextDestroyedCallback: () => void = () => {};
|
private _contextDestroyedCallback: () => void = () => {};
|
||||||
private _executionContextDestroyedPromise: Promise<unknown>;
|
private readonly _executionContextDestroyedPromise: Promise<unknown>;
|
||||||
_jsonStringifyObjectId: Protocol.Runtime.RemoteObjectId | undefined;
|
|
||||||
|
|
||||||
constructor(client: WKSession, contextId: number | undefined) {
|
constructor(session: WKSession, contextId: number | undefined) {
|
||||||
this._session = client;
|
this._session = session;
|
||||||
this._contextId = contextId;
|
this._contextId = contextId;
|
||||||
this._executionContextDestroyedPromise = new Promise((resolve, reject) => {
|
this._executionContextDestroyedPromise = new Promise((resolve, reject) => {
|
||||||
this._contextDestroyedCallback = resolve;
|
this._contextDestroyedCallback = resolve;
|
||||||
@ -45,37 +44,65 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async evaluate(context: js.ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
|
async evaluate(context: js.ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
|
||||||
|
try {
|
||||||
|
let response = await this._evaluateRemoteObject(pageFunction, args);
|
||||||
|
if (response.result.type === 'object' && response.result.className === 'Promise') {
|
||||||
|
response = await Promise.race([
|
||||||
|
this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
|
||||||
|
this._session.send('Runtime.awaitPromise', {
|
||||||
|
promiseObjectId: response.result.objectId,
|
||||||
|
returnByValue: false
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (response.wasThrown)
|
||||||
|
throw new Error('Evaluation failed: ' + response.result.description);
|
||||||
|
if (!returnByValue)
|
||||||
|
return context._createHandle(response.result);
|
||||||
|
if (response.result.objectId)
|
||||||
|
return await this._returnObjectByValue(response.result.objectId);
|
||||||
|
return valueFromRemoteObject(response.result);
|
||||||
|
} catch (error) {
|
||||||
|
if (isSwappedOutError(error) || error.message.includes('Missing injected script for given'))
|
||||||
|
throw new Error('Execution context was destroyed, most likely because of a navigation.');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _evaluateRemoteObject(pageFunction: Function | string, args: any[]): Promise<any> {
|
||||||
if (helper.isString(pageFunction)) {
|
if (helper.isString(pageFunction)) {
|
||||||
const contextId = this._contextId;
|
const contextId = this._contextId;
|
||||||
const expression: string = pageFunction as string;
|
const expression: string = pageFunction as string;
|
||||||
const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) ? expression : expression + '\n' + suffix;
|
const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) ? expression : expression + '\n' + suffix;
|
||||||
return this._session.send('Runtime.evaluate', {
|
return await this._session.send('Runtime.evaluate', {
|
||||||
expression: expressionWithSourceUrl,
|
expression: expressionWithSourceUrl,
|
||||||
contextId,
|
contextId,
|
||||||
returnByValue: false,
|
returnByValue: false,
|
||||||
emulateUserGesture: true
|
emulateUserGesture: true
|
||||||
}).then(response => {
|
});
|
||||||
if (response.result.type === 'object' && response.result.className === 'Promise') {
|
|
||||||
return Promise.race([
|
|
||||||
this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
|
|
||||||
this._awaitPromise(response.result.objectId!),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}).then(response => {
|
|
||||||
if (response.wasThrown)
|
|
||||||
throw new Error('Evaluation failed: ' + response.result.description);
|
|
||||||
if (!returnByValue)
|
|
||||||
return context._createHandle(response.result);
|
|
||||||
if (response.result.objectId)
|
|
||||||
return this._returnObjectByValue(response.result.objectId);
|
|
||||||
return valueFromRemoteObject(response.result);
|
|
||||||
}).catch(rewriteError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof pageFunction !== 'function')
|
if (typeof pageFunction !== 'function')
|
||||||
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
|
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const callParams = this._serializeFunctionAndArguments(pageFunction, args);
|
||||||
|
const thisObjectId = await this._contextGlobalObjectId();
|
||||||
|
return await this._session.send('Runtime.callFunctionOn', {
|
||||||
|
functionDeclaration: callParams.functionText + '\n' + suffix + '\n',
|
||||||
|
objectId: thisObjectId,
|
||||||
|
arguments: callParams.callArguments,
|
||||||
|
returnByValue: false,
|
||||||
|
emulateUserGesture: true
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON'))
|
||||||
|
err.message += ' Are you passing a nested JSHandle?';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _serializeFunctionAndArguments(pageFunction: Function, args: any[]): { functionText: string, callArguments: Protocol.Runtime.CallArgument[] } {
|
||||||
let functionText = pageFunction.toString();
|
let functionText = pageFunction.toString();
|
||||||
try {
|
try {
|
||||||
new Function('(' + functionText + ')');
|
new Function('(' + functionText + ')');
|
||||||
@ -110,43 +137,8 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
|||||||
} else {
|
} else {
|
||||||
serializableArgs = args;
|
serializableArgs = args;
|
||||||
}
|
}
|
||||||
|
const serialized = serializableArgs.map((arg: any) => this._convertArgument(arg));
|
||||||
let thisObjectId;
|
return { functionText, callArguments: serialized };
|
||||||
try {
|
|
||||||
thisObjectId = await this._contextGlobalObjectId();
|
|
||||||
} catch (error) {
|
|
||||||
if (error.message.includes('Missing injected script for given'))
|
|
||||||
throw new Error('Execution context was destroyed, most likely because of a navigation.');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._session.send('Runtime.callFunctionOn', {
|
|
||||||
functionDeclaration: functionText + '\n' + suffix + '\n',
|
|
||||||
objectId: thisObjectId,
|
|
||||||
arguments: serializableArgs.map((arg: any) => this._convertArgument(arg)),
|
|
||||||
returnByValue: false,
|
|
||||||
emulateUserGesture: true
|
|
||||||
}).catch(err => {
|
|
||||||
if (err instanceof TypeError && err.message.startsWith('Converting circular structure to JSON'))
|
|
||||||
err.message += ' Are you passing a nested JSHandle?';
|
|
||||||
throw err;
|
|
||||||
}).then(response => {
|
|
||||||
if (response.result.type === 'object' && response.result.className === 'Promise') {
|
|
||||||
return Promise.race([
|
|
||||||
this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
|
|
||||||
this._awaitPromise(response.result.objectId!),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}).then(response => {
|
|
||||||
if (response.wasThrown)
|
|
||||||
throw new Error('Evaluation failed: ' + response.result.description);
|
|
||||||
if (!returnByValue)
|
|
||||||
return context._createHandle(response.result);
|
|
||||||
if (response.result.objectId)
|
|
||||||
return this._returnObjectByValue(response.result.objectId).catch(() => undefined);
|
|
||||||
return valueFromRemoteObject(response.result);
|
|
||||||
}).catch(rewriteError);
|
|
||||||
|
|
||||||
function unserializableToString(arg: any) {
|
function unserializableToString(arg: any) {
|
||||||
if (Object.is(arg, -0))
|
if (Object.is(arg, -0))
|
||||||
@ -183,56 +175,36 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewriteError(error: Error): Protocol.Runtime.evaluateReturnValue {
|
|
||||||
if (error.message.includes('Missing injected script for given'))
|
|
||||||
throw new Error('Execution context was destroyed, most likely because of a navigation.');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _contextGlobalObjectId() {
|
private _contextGlobalObjectId() : Promise<Protocol.Runtime.RemoteObjectId> {
|
||||||
if (!this._globalObjectId) {
|
if (!this._globalObjectIdPromise) {
|
||||||
this._globalObjectId = this._session.send('Runtime.evaluate', {
|
this._globalObjectIdPromise = this._session.send('Runtime.evaluate', {
|
||||||
expression: 'this',
|
expression: 'this',
|
||||||
contextId: this._contextId
|
contextId: this._contextId
|
||||||
}).catch(e => {
|
|
||||||
if (isSwappedOutError(e))
|
|
||||||
throw new Error('Execution context was destroyed, most likely because of a navigation.');
|
|
||||||
throw e;
|
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
return response.result.objectId!;
|
return response.result.objectId!;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this._globalObjectId;
|
return this._globalObjectIdPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _awaitPromise(objectId: Protocol.Runtime.RemoteObjectId) {
|
private async _returnObjectByValue(objectId: Protocol.Runtime.RemoteObjectId) : Promise<any> {
|
||||||
return this._session.send('Runtime.awaitPromise', {
|
try {
|
||||||
promiseObjectId: objectId,
|
const serializeResponse = await this._session.send('Runtime.callFunctionOn', {
|
||||||
returnByValue: false
|
// Serialize object using standard JSON implementation to correctly pass 'undefined'.
|
||||||
}).catch(e => {
|
functionDeclaration: 'function(){return this}\n' + suffix + '\n',
|
||||||
if (isSwappedOutError(e))
|
objectId: objectId,
|
||||||
return contextDestroyedResult;
|
returnByValue: true
|
||||||
throw e;
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _returnObjectByValue(objectId: Protocol.Runtime.RemoteObjectId) {
|
|
||||||
return this._session.send('Runtime.callFunctionOn', {
|
|
||||||
// Serialize object using standard JSON implementation to correctly pass 'undefined'.
|
|
||||||
functionDeclaration: 'function(){return this}\n' + suffix + '\n',
|
|
||||||
objectId: objectId,
|
|
||||||
returnByValue: true
|
|
||||||
}).catch(e => {
|
|
||||||
if (isSwappedOutError(e))
|
|
||||||
return contextDestroyedResult;
|
|
||||||
throw e;
|
|
||||||
}).then(serializeResponse => {
|
|
||||||
if (serializeResponse.wasThrown)
|
if (serializeResponse.wasThrown)
|
||||||
throw new Error('Serialization failed: ' + serializeResponse.result.description);
|
return undefined;
|
||||||
return serializeResponse.result.value;
|
return serializeResponse.result.value;
|
||||||
});
|
} catch (e) {
|
||||||
|
if (isSwappedOutError(e))
|
||||||
|
return contextDestroyedResult;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
|
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user