fix(tracing): do not stall while capturing snapshot with an open dialog (#8328)

This commit is contained in:
Dmitry Gozman 2021-08-19 18:20:15 -07:00 committed by GitHub
parent 80dded6ccf
commit 9c96468b9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 3 deletions

View File

@ -38,6 +38,7 @@ export class Dialog extends SdkObject {
this._message = message;
this._onHandle = onHandle;
this._defaultValue = defaultValue || '';
this._page._frameManager.dialogDidOpen();
}
type(): string {
@ -55,12 +56,14 @@ export class Dialog extends SdkObject {
async accept(promptText: string | undefined) {
assert(!this._handled, 'Cannot accept dialog which is already handled!');
this._handled = true;
this._page._frameManager.dialogWillClose();
await this._onHandle(true, promptText);
}
async dismiss() {
assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
this._handled = true;
this._page._frameManager.dialogWillClose();
await this._onHandle(false);
}
}

View File

@ -74,6 +74,7 @@ export class FrameManager {
readonly _signalBarriers = new Set<SignalBarrier>();
private _webSockets = new Map<string, network.WebSocket>();
readonly _responses: network.Response[] = [];
_dialogCounter = 0;
constructor(page: Page) {
this._page = page;
@ -297,6 +298,17 @@ export class FrameManager {
this._page._browserContext.emit(BrowserContext.Events.RequestFailed, request);
}
dialogDidOpen() {
// Any ongoing evaluations will be stalled until the dialog is closed.
for (const frame of this._frames.values())
frame._invalidateNonStallingEvaluations('JavaScript dialog interrupted evaluation');
this._dialogCounter++;
}
dialogWillClose() {
this._dialogCounter--;
}
removeChildFramesRecursively(frame: Frame) {
for (const child of frame.childFrames())
this._removeFramesRecursively(child);
@ -461,17 +473,17 @@ export class Frame extends SdkObject {
setPendingDocument(documentInfo: DocumentInfo | undefined) {
this._pendingDocument = documentInfo;
if (documentInfo)
this._invalidateNonStallingEvaluations();
this._invalidateNonStallingEvaluations('Navigation interrupted the evaluation');
}
pendingDocument(): DocumentInfo | undefined {
return this._pendingDocument;
}
private async _invalidateNonStallingEvaluations() {
_invalidateNonStallingEvaluations(message: string) {
if (!this._nonStallingEvaluations)
return;
const error = new Error('Navigation interrupted the evaluation');
const error = new Error(message);
for (const callback of this._nonStallingEvaluations)
callback(error);
}
@ -479,6 +491,8 @@ export class Frame extends SdkObject {
async nonStallingRawEvaluateInExistingMainContext(expression: string): Promise<any> {
if (this._pendingDocument)
throw new Error('Frame is currently attempting a navigation');
if (this._page._frameManager._dialogCounter)
throw new Error('Open JavaScript dialog prevents evaluation');
const context = this._existingMainContext();
if (!context)
throw new Error('Frame does not yet have a main execution context');

View File

@ -264,6 +264,16 @@ test('should export trace concurrently to second navigation', async ({ context,
}
});
test('should not hang for clicks that open dialogs', async ({ context, page }) => {
await context.tracing.start({ screenshots: true, snapshots: true });
const dialogPromise = page.waitForEvent('dialog');
await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`);
await page.click('div', { timeout: 2000 }).catch(() => {});
const dialog = await dialogPromise;
await dialog.dismiss();
await context.tracing.stop();
});
async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> {
const entries = await new Promise<any[]>(f => {
const entries: Promise<any>[] = [];