mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-07 11:46:42 +03:00
chore: migrate waitForFunction to not use rerunnable task (#19730)
This commit is contained in:
parent
0b223b9036
commit
0fba4d5611
@ -45,7 +45,6 @@ import { asLocator } from './isomorphic/locatorGenerators';
|
|||||||
type ContextData = {
|
type ContextData = {
|
||||||
contextPromise: ManualPromise<dom.FrameExecutionContext | Error>;
|
contextPromise: ManualPromise<dom.FrameExecutionContext | Error>;
|
||||||
context: dom.FrameExecutionContext | null;
|
context: dom.FrameExecutionContext | null;
|
||||||
rerunnableTasks: Set<RerunnableTask<any>>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type DocumentInfo = {
|
type DocumentInfo = {
|
||||||
@ -509,8 +508,8 @@ export class Frame extends SdkObject {
|
|||||||
|
|
||||||
this._detachedPromise = new Promise<void>(x => this._detachedCallback = x);
|
this._detachedPromise = new Promise<void>(x => this._detachedCallback = x);
|
||||||
|
|
||||||
this._contextData.set('main', { contextPromise: new ManualPromise(), context: null, rerunnableTasks: new Set() });
|
this._contextData.set('main', { contextPromise: new ManualPromise(), context: null });
|
||||||
this._contextData.set('utility', { contextPromise: new ManualPromise(), context: null, rerunnableTasks: new Set() });
|
this._contextData.set('utility', { contextPromise: new ManualPromise(), context: null });
|
||||||
this._setContext('main', null);
|
this._setContext('main', null);
|
||||||
this._setContext('utility', null);
|
this._setContext('utility', null);
|
||||||
|
|
||||||
@ -1477,8 +1476,12 @@ export class Frame extends SdkObject {
|
|||||||
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);
|
||||||
expression = js.normalizeEvaluationExpression(expression, isFunction);
|
expression = js.normalizeEvaluationExpression(expression, isFunction);
|
||||||
const task: dom.SchedulableTask<R> = injectedScript => injectedScript.evaluateHandle((injectedScript, { expression, isFunction, polling, arg }) => {
|
return controller.run(async progress => {
|
||||||
const predicate = (arg: any): R => {
|
return this.retryWithProgressAndTimeouts(progress, [100], async () => {
|
||||||
|
const context = await this._mainContext();
|
||||||
|
const injectedScript = await context.injectedScript();
|
||||||
|
const handle = await injectedScript.evaluateHandle((injected, { expression, isFunction, polling, arg }) => {
|
||||||
|
const predicate = (): R => {
|
||||||
let result = self.eval(expression);
|
let result = self.eval(expression);
|
||||||
if (isFunction === true) {
|
if (isFunction === true) {
|
||||||
result = result(arg);
|
result = result(arg);
|
||||||
@ -1491,13 +1494,37 @@ export class Frame extends SdkObject {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fulfill: (result: R) => void;
|
||||||
|
let reject: (error: Error) => void;
|
||||||
|
let aborted = false;
|
||||||
|
const result = new Promise<R>((f, r) => { fulfill = f; reject = r; });
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
|
if (aborted)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
const success = predicate();
|
||||||
|
if (success) {
|
||||||
|
fulfill(success);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (typeof polling !== 'number')
|
if (typeof polling !== 'number')
|
||||||
return injectedScript.pollRaf(progress => predicate(arg) || progress.continuePolling);
|
requestAnimationFrame(next);
|
||||||
return injectedScript.pollInterval(polling, progress => predicate(arg) || progress.continuePolling);
|
else
|
||||||
|
setTimeout(next, polling);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
return { result, abort: () => aborted = true };
|
||||||
}, { expression, isFunction, polling: options.pollingInterval, arg });
|
}, { expression, isFunction, polling: options.pollingInterval, arg });
|
||||||
return controller.run(
|
progress.cleanupWhenAborted(() => handle.evaluate(h => h.abort()).catch(() => {}));
|
||||||
progress => this._scheduleRerunnableHandleTask(progress, world, task),
|
return handle.evaluateHandle(h => h.result);
|
||||||
this._page._timeoutSettings.timeout(options));
|
});
|
||||||
|
}, this._page._timeoutSettings.timeout(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForFunctionValueInUtility<R>(progress: Progress, pageFunction: js.Func1<any, R>) {
|
async waitForFunctionValueInUtility<R>(progress: Progress, pageFunction: js.Func1<any, R>) {
|
||||||
@ -1540,8 +1567,6 @@ export class Frame extends SdkObject {
|
|||||||
if (data.context)
|
if (data.context)
|
||||||
data.context.contextDestroyed(error);
|
data.context.contextDestroyed(error);
|
||||||
data.contextPromise.resolve(error);
|
data.contextPromise.resolve(error);
|
||||||
for (const rerunnableTask of data.rerunnableTasks)
|
|
||||||
rerunnableTask.terminate(error);
|
|
||||||
}
|
}
|
||||||
if (this._parentFrame)
|
if (this._parentFrame)
|
||||||
this._parentFrame._childFrames.delete(this);
|
this._parentFrame._childFrames.delete(this);
|
||||||
@ -1577,27 +1602,14 @@ export class Frame extends SdkObject {
|
|||||||
}, this._page._timeoutSettings.timeout(options));
|
}, this._page._timeoutSettings.timeout(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _scheduleRerunnableHandleTask<T>(progress: Progress, world: types.World, task: dom.SchedulableTask<T>): Promise<js.SmartHandle<T>> {
|
|
||||||
const data = this._contextData.get(world)!;
|
|
||||||
const rerunnableTask = new RerunnableTask<T>(data, progress, task, false /* returnByValue */);
|
|
||||||
if (this._detached)
|
|
||||||
rerunnableTask.terminate(new Error('waitForFunction failed: frame got detached.'));
|
|
||||||
if (data.context)
|
|
||||||
rerunnableTask.rerun(data.context);
|
|
||||||
return rerunnableTask.handlePromise!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setContext(world: types.World, context: dom.FrameExecutionContext | null) {
|
private _setContext(world: types.World, context: dom.FrameExecutionContext | null) {
|
||||||
const data = this._contextData.get(world)!;
|
const data = this._contextData.get(world)!;
|
||||||
data.context = context;
|
data.context = context;
|
||||||
if (context) {
|
if (context)
|
||||||
data.contextPromise.resolve(context);
|
data.contextPromise.resolve(context);
|
||||||
for (const rerunnableTask of data.rerunnableTasks)
|
else
|
||||||
rerunnableTask.rerun(context);
|
|
||||||
} else {
|
|
||||||
data.contextPromise = new ManualPromise();
|
data.contextPromise = new ManualPromise();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_contextCreated(world: types.World, context: dom.FrameExecutionContext) {
|
_contextCreated(world: types.World, context: dom.FrameExecutionContext) {
|
||||||
const data = this._contextData.get(world)!;
|
const data = this._contextData.get(world)!;
|
||||||
@ -1706,66 +1718,6 @@ export class Frame extends SdkObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RerunnableTask<T> {
|
|
||||||
readonly promise: ManualPromise<T> | undefined;
|
|
||||||
readonly handlePromise: ManualPromise<js.SmartHandle<T>> | undefined;
|
|
||||||
private _task: dom.SchedulableTask<T>;
|
|
||||||
private _progress: Progress;
|
|
||||||
private _returnByValue: boolean;
|
|
||||||
private _contextData: ContextData;
|
|
||||||
|
|
||||||
constructor(data: ContextData, progress: Progress, task: dom.SchedulableTask<T>, returnByValue: boolean) {
|
|
||||||
this._task = task;
|
|
||||||
this._progress = progress;
|
|
||||||
this._returnByValue = returnByValue;
|
|
||||||
if (returnByValue)
|
|
||||||
this.promise = new ManualPromise<T>();
|
|
||||||
else
|
|
||||||
this.handlePromise = new ManualPromise<js.SmartHandle<T>>();
|
|
||||||
this._contextData = data;
|
|
||||||
this._contextData.rerunnableTasks.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
terminate(error: Error) {
|
|
||||||
this._reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _resolve(value: T | js.SmartHandle<T>) {
|
|
||||||
if (this.promise)
|
|
||||||
this.promise.resolve(value as T);
|
|
||||||
if (this.handlePromise)
|
|
||||||
this.handlePromise.resolve(value as js.SmartHandle<T>);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _reject(error: Error) {
|
|
||||||
if (this.promise)
|
|
||||||
this.promise.reject(error);
|
|
||||||
if (this.handlePromise)
|
|
||||||
this.handlePromise.reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
async rerun(context: dom.FrameExecutionContext) {
|
|
||||||
try {
|
|
||||||
const injectedScript = await context.injectedScript();
|
|
||||||
const pollHandler = new dom.InjectedScriptPollHandler(this._progress, await this._task(injectedScript));
|
|
||||||
const result = this._returnByValue ? await pollHandler.finish() : await pollHandler.finishHandle();
|
|
||||||
this._contextData.rerunnableTasks.delete(this);
|
|
||||||
this._resolve(result);
|
|
||||||
} catch (e) {
|
|
||||||
if (js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) {
|
|
||||||
this._contextData.rerunnableTasks.delete(this);
|
|
||||||
this._reject(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlike other places, we don't check frame for being detached since the whole scope of this
|
|
||||||
// evaluation is within the frame's execution context. So we only let JavaScript errors and
|
|
||||||
// session termination errors go through.
|
|
||||||
|
|
||||||
// We will try again in the new execution context.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignalBarrier {
|
class SignalBarrier {
|
||||||
private _progress: Progress | null;
|
private _progress: Progress | null;
|
||||||
private _protectCount = 0;
|
private _protectCount = 0;
|
||||||
|
@ -387,17 +387,7 @@ export class InjectedScript {
|
|||||||
return this.poll(predicate, next => requestAnimationFrame(next));
|
return this.poll(predicate, next => requestAnimationFrame(next));
|
||||||
}
|
}
|
||||||
|
|
||||||
pollInterval<T>(pollInterval: number, predicate: Predicate<T>): InjectedScriptPoll<T> {
|
private poll<T>(predicate: Predicate<T>, scheduleNext: (next: () => void) => void): InjectedScriptPoll<T> {
|
||||||
return this.poll(predicate, next => setTimeout(next, pollInterval));
|
|
||||||
}
|
|
||||||
|
|
||||||
pollLogScale<T>(predicate: Predicate<T>): InjectedScriptPoll<T> {
|
|
||||||
const pollIntervals = [100, 250, 500];
|
|
||||||
let attempts = 0;
|
|
||||||
return this.poll(predicate, next => setTimeout(next, pollIntervals[attempts++] || 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
poll<T>(predicate: Predicate<T>, scheduleNext: (next: () => void) => void): InjectedScriptPoll<T> {
|
|
||||||
return this._runAbortableTask(progress => {
|
return this._runAbortableTask(progress => {
|
||||||
let fulfill: (result: T) => void;
|
let fulfill: (result: T) => void;
|
||||||
let reject: (error: Error) => void;
|
let reject: (error: Error) => void;
|
||||||
|
Loading…
Reference in New Issue
Block a user