chore: refactor deferred promise (#8759)

This commit is contained in:
jrandolf 2022-08-09 13:29:12 +02:00 committed by GitHub
parent 50bcff2ec2
commit 8be8f5ff72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 41 deletions

View File

@ -28,7 +28,9 @@ import {getQueryHandlerAndSelector} from './QueryHandler.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js'; import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import { import {
createDeferredPromise,
debugError, debugError,
DeferredPromise,
importFS, importFS,
isNumber, isNumber,
isString, isString,
@ -75,8 +77,7 @@ export class DOMWorld {
#frame: Frame; #frame: Frame;
#timeoutSettings: TimeoutSettings; #timeoutSettings: TimeoutSettings;
#documentPromise: Promise<ElementHandle<Document>> | null = null; #documentPromise: Promise<ElementHandle<Document>> | null = null;
#contextPromise: Promise<ExecutionContext> | null = null; #contextPromise: DeferredPromise<ExecutionContext> = createDeferredPromise();
#contextResolveCallback: ((x: ExecutionContext) => void) | null = null;
#detached = false; #detached = false;
// Set of bindings that have been registered in the current context. // Set of bindings that have been registered in the current context.
@ -110,7 +111,6 @@ export class DOMWorld {
this.#frameManager = frameManager; this.#frameManager = frameManager;
this.#frame = frame; this.#frame = frame;
this.#timeoutSettings = timeoutSettings; this.#timeoutSettings = timeoutSettings;
this._setContext(null);
this.#client.on('Runtime.bindingCalled', this.#onBindingCalled); this.#client.on('Runtime.bindingCalled', this.#onBindingCalled);
} }
@ -118,28 +118,25 @@ export class DOMWorld {
return this.#frame; return this.#frame;
} }
async _setContext(context: ExecutionContext | null): Promise<void> { clearContext(): void {
if (context) { this.#documentPromise = null;
this.#contextPromise = createDeferredPromise();
}
setContext(context: ExecutionContext): void {
assert( assert(
this.#contextResolveCallback, this.#contextPromise,
`ExecutionContext ${context._contextId} has already been set.` `ExecutionContext ${context._contextId} has already been set.`
); );
this.#ctxBindings.clear(); this.#ctxBindings.clear();
this.#contextResolveCallback?.call(null, context); this.#contextPromise.resolve(context);
this.#contextResolveCallback = null;
for (const waitTask of this._waitTasks) { for (const waitTask of this._waitTasks) {
waitTask.rerun(); waitTask.rerun();
} }
} else {
this.#documentPromise = null;
this.#contextPromise = new Promise(fulfill => {
this.#contextResolveCallback = fulfill;
});
}
} }
_hasContext(): boolean { hasContext(): boolean {
return !this.#contextResolveCallback; return this.#contextPromise.resolved();
} }
_detach(): void { _detach(): void {
@ -619,7 +616,7 @@ export class DOMWorld {
event: Protocol.Runtime.BindingCalledEvent event: Protocol.Runtime.BindingCalledEvent
): Promise<void> => { ): Promise<void> => {
let payload: {type: string; name: string; seq: number; args: unknown[]}; let payload: {type: string; name: string; seq: number; args: unknown[]};
if (!this._hasContext()) { if (!this.hasContext()) {
return; return;
} }
const context = await this.executionContext(); const context = await this.executionContext();

View File

@ -397,7 +397,8 @@ export class FrameManager extends EventEmitter {
return complete(parentFrame); return complete(parentFrame);
} }
if (this.#framesPendingTargetInit.has(parentFrameId)) { const frame = this.#framesPendingTargetInit.get(parentFrameId);
if (frame) {
if (!this.#framesPendingAttachment.has(frameId)) { if (!this.#framesPendingAttachment.has(frameId)) {
this.#framesPendingAttachment.set( this.#framesPendingAttachment.set(
frameId, frameId,
@ -406,7 +407,7 @@ export class FrameManager extends EventEmitter {
) )
); );
} }
this.#framesPendingTargetInit.get(parentFrameId)!.promise.then(() => { frame.then(() => {
complete(this.#frames.get(parentFrameId)!); complete(this.#frames.get(parentFrameId)!);
this.#framesPendingAttachment.get(frameId)?.resolve(); this.#framesPendingAttachment.get(frameId)?.resolve();
this.#framesPendingAttachment.delete(frameId); this.#framesPendingAttachment.delete(frameId);
@ -455,8 +456,9 @@ export class FrameManager extends EventEmitter {
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame); this.emit(FrameManagerEmittedEvents.FrameNavigated, frame);
}; };
if (this.#framesPendingAttachment.has(frameId)) { const pendingFrame = this.#framesPendingAttachment.get(frameId);
this.#framesPendingAttachment.get(frameId)!.promise.then(() => { if (pendingFrame) {
pendingFrame.then(() => {
complete(isMainFrame ? this.#mainFrame : this.#frames.get(frameId)); complete(isMainFrame ? this.#mainFrame : this.#frames.get(frameId));
}); });
} else { } else {
@ -539,7 +541,7 @@ export class FrameManager extends EventEmitter {
world = frame._mainWorld; world = frame._mainWorld;
} else if ( } else if (
contextPayload.name === UTILITY_WORLD_NAME && contextPayload.name === UTILITY_WORLD_NAME &&
!frame._secondaryWorld._hasContext() !frame._secondaryWorld.hasContext()
) { ) {
// In case of multiple sessions to the same target, there's a race between // In case of multiple sessions to the same target, there's a race between
// connections so we might end up creating multiple isolated worlds. // connections so we might end up creating multiple isolated worlds.
@ -553,7 +555,7 @@ export class FrameManager extends EventEmitter {
world world
); );
if (world) { if (world) {
world._setContext(context); world.setContext(context);
} }
const key = `${session.id()}:${contextPayload.id}`; const key = `${session.id()}:${contextPayload.id}`;
this.#contextIdToContext.set(key, context); this.#contextIdToContext.set(key, context);
@ -570,7 +572,7 @@ export class FrameManager extends EventEmitter {
} }
this.#contextIdToContext.delete(key); this.#contextIdToContext.delete(key);
if (context._world) { if (context._world) {
context._world._setContext(null); context._world.clearContext();
} }
} }
@ -582,7 +584,7 @@ export class FrameManager extends EventEmitter {
continue; continue;
} }
if (context._world) { if (context._world) {
context._world._setContext(null); context._world.clearContext();
} }
this.#contextIdToContext.delete(key); this.#contextIdToContext.delete(key);
} }

View File

@ -143,7 +143,7 @@ export class NetworkManager extends EventEmitter {
*/ */
initialize(): Promise<void> { initialize(): Promise<void> {
if (this.#deferredInitPromise) { if (this.#deferredInitPromise) {
return this.#deferredInitPromise.promise; return this.#deferredInitPromise;
} }
this.#deferredInitPromise = createDeferredPromiseWithTimer<void>( this.#deferredInitPromise = createDeferredPromiseWithTimer<void>(
'NetworkManager initialization timed out', 'NetworkManager initialization timed out',
@ -157,14 +157,15 @@ export class NetworkManager extends EventEmitter {
: null, : null,
this.#client.send('Network.enable'), this.#client.send('Network.enable'),
]); ]);
const deferredInitPromise = this.#deferredInitPromise;
init init
.then(() => { .then(() => {
this.#deferredInitPromise?.resolve(); deferredInitPromise.resolve();
}) })
.catch(err => { .catch(err => {
return this.#deferredInitPromise?.reject(err); deferredInitPromise.reject(err);
}); });
return this.#deferredInitPromise.promise; return this.#deferredInitPromise;
} }
async authenticate(credentials?: Credentials): Promise<void> { async authenticate(credentials?: Credentials): Promise<void> {

View File

@ -527,11 +527,11 @@ export function isErrnoException(obj: unknown): obj is NodeJS.ErrnoException {
/** /**
* @internal * @internal
*/ */
export type DeferredPromise<T> = { export interface DeferredPromise<T> extends Promise<T> {
promise: Promise<T>; resolved: () => boolean;
resolve: (_: T) => void; resolve: (_: T) => void;
reject: (_: Error) => void; reject: (_: Error) => void;
}; }
/** /**
* Creates an returns a promise along with the resolve/reject functions. * Creates an returns a promise along with the resolve/reject functions.
@ -545,6 +545,7 @@ export function createDeferredPromiseWithTimer<T>(
timeoutMessage: string, timeoutMessage: string,
timeout = 5000 timeout = 5000
): DeferredPromise<T> { ): DeferredPromise<T> {
let isResolved = false;
let resolver = (_: T): void => {}; let resolver = (_: T): void => {};
let rejector = (_: Error) => {}; let rejector = (_: Error) => {};
const taskPromise = new Promise<T>((resolve, reject) => { const taskPromise = new Promise<T>((resolve, reject) => {
@ -554,15 +555,45 @@ export function createDeferredPromiseWithTimer<T>(
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
rejector(new Error(timeoutMessage)); rejector(new Error(timeoutMessage));
}, timeout); }, timeout);
return { return Object.assign(taskPromise, {
promise: taskPromise, resolved: () => {
return isResolved;
},
resolve: (value: T) => { resolve: (value: T) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
isResolved = true;
resolver(value); resolver(value);
}, },
reject: (err: Error) => { reject: (err: Error) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
rejector(err); rejector(err);
}, },
}; });
}
/**
* Creates an returns a promise along with the resolve/reject functions.
*
* @internal
*/
export function createDeferredPromise<T>(): DeferredPromise<T> {
let isResolved = false;
let resolver = (_: T): void => {};
let rejector = (_: Error) => {};
const taskPromise = new Promise<T>((resolve, reject) => {
resolver = resolve;
rejector = reject;
});
return Object.assign(taskPromise, {
resolved: () => {
return isResolved;
},
resolve: (value: T) => {
isResolved = true;
resolver(value);
},
reject: (err: Error) => {
rejector(err);
},
});
} }