chore: simplify polls and evaluates in dom.ts (#9941)

This commit is contained in:
Dmitry Gozman 2021-11-01 15:59:47 -07:00 committed by GitHub
parent a2c414cd88
commit 4e52b64619
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -62,12 +62,6 @@ export class FrameExecutionContext extends js.ExecutionContext {
return js.evaluateExpression(this, true /* returnByValue */, expression, isFunction, arg);
}
async evaluateAndWaitForSignals<Arg, R>(pageFunction: js.Func1<Arg, R>, arg?: Arg): Promise<R> {
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return this.evaluate(pageFunction, arg);
});
}
async evaluateExpressionAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg?: any): Promise<any> {
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return this.evaluateExpression(expression, isFunction, arg);
@ -131,11 +125,6 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return this;
}
private async _evaluateInMainAndWaitForSignals<R, Arg>(pageFunction: js.Func1<[js.JSHandle<InjectedScript>, ElementHandle<T>, Arg], R>, arg: Arg): Promise<R> {
const main = await this._context.frame._mainContext();
return main.evaluateAndWaitForSignals(pageFunction, [await main.injectedScript(), this, arg]);
}
async evaluateInUtility<R, Arg>(pageFunction: js.Func1<[js.JSHandle<InjectedScript>, ElementHandle<T>, Arg], R>, arg: Arg): Promise<R | 'error:notconnected'> {
try {
const utility = await this._context.frame._utilityContext();
@ -158,6 +147,19 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
}
async evaluatePoll<R, Arg>(progress: Progress, pageFunction: js.Func1<[js.JSHandle<InjectedScript>, ElementHandle<T>, Arg], InjectedScriptPoll<R>>, arg: Arg): Promise<R | 'error:notconnected'> {
try {
const utility = await this._context.frame._utilityContext();
const poll = await utility.evaluateHandle(pageFunction, [await utility.injectedScript(), this, arg]);
const pollHandler = new InjectedScriptPollHandler(progress, poll);
return await pollHandler.finish();
} catch (e) {
if (js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e))
throw e;
return 'error:notconnected';
}
}
async ownerFrame(): Promise<frames.Frame | null> {
const frameId = await this._page._delegate.getOwnerFrame(this);
if (!frameId)
@ -225,8 +227,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async dispatchEvent(type: string, eventInit: Object = {}) {
await this._evaluateInMainAndWaitForSignals(([injected, node, { type, eventInit }]) =>
injected.dispatchEvent(node, type, eventInit), { type, eventInit });
const main = await this._context.frame._mainContext();
await this._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return main.evaluate(([injected, node, { type, eventInit }]) => injected.dispatchEvent(node, type, eventInit), [await main.injectedScript(), this, { type, eventInit }] as const);
});
await this._page._doSlowMo();
}
@ -498,13 +502,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.throwIfAborted(); // Avoid action that has side-effects.
progress.log(' selecting specified option(s)');
const poll = await this.evaluateHandleInUtility(([injected, node, { optionsToSelect, force }]) => {
const result = await this.evaluatePoll(progress, ([injected, node, { optionsToSelect, force }]) => {
return injected.waitForElementStatesAndPerformAction(node, ['visible', 'enabled'], force, injected.selectOptions.bind(injected, optionsToSelect));
}, { optionsToSelect, force: options.force });
if (poll === 'error:notconnected')
return poll;
const pollHandler = new InjectedScriptPollHandler(progress, poll);
const result = await pollHandler.finishMaybeNotConnected();
await this._page._doSlowMo();
return result;
});
@ -523,13 +523,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
await progress.beforeInputAction(this);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.log(' waiting for element to be visible, enabled and editable');
const poll = await this.evaluateHandleInUtility(([injected, node, { value, force }]) => {
const filled = await this.evaluatePoll(progress, ([injected, node, { value, force }]) => {
return injected.waitForElementStatesAndPerformAction(node, ['visible', 'enabled', 'editable'], force, injected.fill.bind(injected, value));
}, { value, force: options.force });
if (poll === 'error:notconnected')
return poll;
const pollHandler = new InjectedScriptPollHandler(progress, poll);
const filled = await pollHandler.finishMaybeNotConnected();
progress.throwIfAborted(); // Avoid action that has side-effects.
if (filled === 'error:notconnected')
return filled;
@ -551,11 +547,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const controller = new ProgressController(metadata, this);
return controller.run(async progress => {
progress.throwIfAborted(); // Avoid action that has side-effects.
const poll = await this.evaluateHandleInUtility(([injected, node, force]) => {
const result = await this.evaluatePoll(progress, ([injected, node, force]) => {
return injected.waitForElementStatesAndPerformAction(node, ['visible'], force, injected.selectText.bind(injected));
}, options.force);
const pollHandler = new InjectedScriptPollHandler(progress, throwRetargetableDOMError(poll));
const result = await pollHandler.finishMaybeNotConnected();
assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
@ -573,24 +567,23 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
if (!payload.mimeType)
payload.mimeType = mime.getType(payload.name) || 'application/octet-stream';
}
const retargeted = await this.evaluateHandleInUtility(([injected, node, multiple]): 'error:notconnected' | Element => {
const result = await this.evaluateHandleInUtility(([injected, node, multiple]): Element | undefined => {
const element = injected.retarget(node, 'follow-label');
if (!element)
return 'error:notconnected';
return;
if (element.tagName !== 'INPUT')
throw injected.createStacklessError('Node is not an HTMLInputElement');
if (multiple && !(element as HTMLInputElement).multiple)
throw injected.createStacklessError('Non-multiple file input can only accept single file');
return element;
}, files.length > 1);
if (retargeted === 'error:notconnected')
return retargeted;
if (!retargeted._objectId)
return retargeted.rawValue() as 'error:notconnected';
if (result === 'error:notconnected' || !result.asElement())
return 'error:notconnected';
const retargeted = result.asElement() as ElementHandle<HTMLInputElement>;
await progress.beforeInputAction(this);
await this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.throwIfAborted(); // Avoid action that has side-effects.
await this._page._delegate.setInputFiles(retargeted as ElementHandle<HTMLInputElement>, files as types.FilePayload[]);
await this._page._delegate.setInputFiles(retargeted, files as types.FilePayload[]);
});
await this._page._doSlowMo();
return 'done';
@ -756,11 +749,10 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const controller = new ProgressController(metadata, this);
return controller.run(async progress => {
progress.log(` waiting for element to be ${state}`);
const poll = await this.evaluateHandleInUtility(([injected, node, state]) => {
const result = await this.evaluatePoll(progress, ([injected, node, state]) => {
return injected.waitForElementStatesAndPerformAction(node, [state], false, () => 'done' as const);
}, state);
const pollHandler = new InjectedScriptPollHandler(progress, throwRetargetableDOMError(poll));
assertDone(throwRetargetableDOMError(await pollHandler.finishMaybeNotConnected()));
assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
@ -800,14 +792,12 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
progress.log(` waiting for element to be visible, enabled and stable`);
else
progress.log(` waiting for element to be visible and stable`);
const poll = await this.evaluateHandleInUtility(([injected, node, { waitForEnabled, force }]) => {
const result = await this.evaluatePoll(progress, ([injected, node, { waitForEnabled, force }]) => {
return injected.waitForElementStatesAndPerformAction(node,
waitForEnabled ? ['visible', 'stable', 'enabled'] : ['visible', 'stable'], force, () => 'done' as const);
}, { waitForEnabled, force });
if (poll === 'error:notconnected')
return poll;
const pollHandler = new InjectedScriptPollHandler(progress, poll);
const result = await pollHandler.finishMaybeNotConnected();
if (result === 'error:notconnected')
return result;
if (waitForEnabled)
progress.log(' element is visible, enabled and stable');
else
@ -876,20 +866,6 @@ export class InjectedScriptPollHandler<T> {
}
}
async finishMaybeNotConnected(): Promise<T | 'error:notconnected'> {
try {
const result = await this._poll!.evaluate(poll => poll.run());
await this._finishInternal();
return result;
} catch (e) {
if (js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e))
throw e;
return 'error:notconnected';
} finally {
await this.cancel();
}
}
private async _finishInternal() {
if (!this._poll)
return;