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

View File

@ -74,6 +74,7 @@ export class FrameManager {
readonly _signalBarriers = new Set<SignalBarrier>(); readonly _signalBarriers = new Set<SignalBarrier>();
private _webSockets = new Map<string, network.WebSocket>(); private _webSockets = new Map<string, network.WebSocket>();
readonly _responses: network.Response[] = []; readonly _responses: network.Response[] = [];
_dialogCounter = 0;
constructor(page: Page) { constructor(page: Page) {
this._page = page; this._page = page;
@ -297,6 +298,17 @@ export class FrameManager {
this._page._browserContext.emit(BrowserContext.Events.RequestFailed, request); 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) { removeChildFramesRecursively(frame: Frame) {
for (const child of frame.childFrames()) for (const child of frame.childFrames())
this._removeFramesRecursively(child); this._removeFramesRecursively(child);
@ -461,17 +473,17 @@ export class Frame extends SdkObject {
setPendingDocument(documentInfo: DocumentInfo | undefined) { setPendingDocument(documentInfo: DocumentInfo | undefined) {
this._pendingDocument = documentInfo; this._pendingDocument = documentInfo;
if (documentInfo) if (documentInfo)
this._invalidateNonStallingEvaluations(); this._invalidateNonStallingEvaluations('Navigation interrupted the evaluation');
} }
pendingDocument(): DocumentInfo | undefined { pendingDocument(): DocumentInfo | undefined {
return this._pendingDocument; return this._pendingDocument;
} }
private async _invalidateNonStallingEvaluations() { _invalidateNonStallingEvaluations(message: string) {
if (!this._nonStallingEvaluations) if (!this._nonStallingEvaluations)
return; return;
const error = new Error('Navigation interrupted the evaluation'); const error = new Error(message);
for (const callback of this._nonStallingEvaluations) for (const callback of this._nonStallingEvaluations)
callback(error); callback(error);
} }
@ -479,6 +491,8 @@ export class Frame extends SdkObject {
async nonStallingRawEvaluateInExistingMainContext(expression: string): Promise<any> { async nonStallingRawEvaluateInExistingMainContext(expression: string): Promise<any> {
if (this._pendingDocument) if (this._pendingDocument)
throw new Error('Frame is currently attempting a navigation'); 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(); const context = this._existingMainContext();
if (!context) if (!context)
throw new Error('Frame does not yet have a main execution 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> }> { async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> {
const entries = await new Promise<any[]>(f => { const entries = await new Promise<any[]>(f => {
const entries: Promise<any>[] = []; const entries: Promise<any>[] = [];