chore: close dialogs upon last disconnect (#36125)
This commit is contained in:
		
							parent
							
								
									bc92cb44fe
								
							
						
					
					
						commit
						8f773a4c06
					
				|  | @ -16,7 +16,6 @@ | ||||||
| 
 | 
 | ||||||
| import { assert } from '../../utils'; | import { assert } from '../../utils'; | ||||||
| import { eventsHelper } from '../utils/eventsHelper'; | import { eventsHelper } from '../utils/eventsHelper'; | ||||||
| import { BrowserContext } from '../browserContext'; |  | ||||||
| import * as dialog from '../dialog'; | import * as dialog from '../dialog'; | ||||||
| import * as dom from '../dom'; | import * as dom from '../dom'; | ||||||
| import { Page } from '../page'; | import { Page } from '../page'; | ||||||
|  | @ -220,7 +219,7 @@ export class BidiPage implements PageDelegate { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private _onUserPromptOpened(event: bidi.BrowsingContext.UserPromptOpenedParameters) { |   private _onUserPromptOpened(event: bidi.BrowsingContext.UserPromptOpenedParameters) { | ||||||
|     this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( |     this._page.browserContext.dialogManager.dialogDidOpen(new dialog.Dialog( | ||||||
|         this._page, |         this._page, | ||||||
|         event.type as dialog.DialogType, |         event.type as dialog.DialogType, | ||||||
|         event.message, |         event.message, | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ import { createGuid } from './utils/crypto'; | ||||||
| import { debugMode } from './utils/debug'; | import { debugMode } from './utils/debug'; | ||||||
| import { Clock } from './clock'; | import { Clock } from './clock'; | ||||||
| import { Debugger } from './debugger'; | import { Debugger } from './debugger'; | ||||||
|  | import { DialogManager } from './dialog'; | ||||||
| import { BrowserContextAPIRequestContext } from './fetch'; | import { BrowserContextAPIRequestContext } from './fetch'; | ||||||
| import { mkdirIfNeeded } from './utils/fileUtils'; | import { mkdirIfNeeded } from './utils/fileUtils'; | ||||||
| import { HarRecorder } from './har/harRecorder'; | import { HarRecorder } from './har/harRecorder'; | ||||||
|  | @ -51,7 +52,6 @@ export abstract class BrowserContext extends SdkObject { | ||||||
|   static Events = { |   static Events = { | ||||||
|     Console: 'console', |     Console: 'console', | ||||||
|     Close: 'close', |     Close: 'close', | ||||||
|     Dialog: 'dialog', |  | ||||||
|     Page: 'page', |     Page: 'page', | ||||||
|     // Can't use just 'error' due to node.js special treatment of error events.
 |     // Can't use just 'error' due to node.js special treatment of error events.
 | ||||||
|     // @see https://nodejs.org/api/events.html#events_error_events
 |     // @see https://nodejs.org/api/events.html#events_error_events
 | ||||||
|  | @ -95,6 +95,7 @@ export abstract class BrowserContext extends SdkObject { | ||||||
|   readonly clock: Clock; |   readonly clock: Clock; | ||||||
|   _clientCertificatesProxy: ClientCertificatesProxy | undefined; |   _clientCertificatesProxy: ClientCertificatesProxy | undefined; | ||||||
|   private _playwrightBindingExposed = false; |   private _playwrightBindingExposed = false; | ||||||
|  |   readonly dialogManager: DialogManager; | ||||||
| 
 | 
 | ||||||
|   constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { |   constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { | ||||||
|     super(browser, 'browser-context'); |     super(browser, 'browser-context'); | ||||||
|  | @ -109,6 +110,7 @@ export abstract class BrowserContext extends SdkObject { | ||||||
|     this.fetchRequest = new BrowserContextAPIRequestContext(this); |     this.fetchRequest = new BrowserContextAPIRequestContext(this); | ||||||
|     this.tracing = new Tracing(this, browser.options.tracesDir); |     this.tracing = new Tracing(this, browser.options.tracesDir); | ||||||
|     this.clock = new Clock(this); |     this.clock = new Clock(this); | ||||||
|  |     this.dialogManager = new DialogManager(this.instrumentation); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   isPersistentContext(): boolean { |   isPersistentContext(): boolean { | ||||||
|  | @ -210,12 +212,8 @@ export abstract class BrowserContext extends SdkObject { | ||||||
|       page = undefined; |       page = undefined; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Unless dialogs are dismissed, setting extra http headers below does not respond.
 |  | ||||||
|     page?.frameManager.setCloseAllOpeningDialogs(true); |  | ||||||
|     await page?.frameManager.closeOpenDialogs(); |  | ||||||
|     // Navigate to about:blank first to ensure no page scripts are running after this point.
 |     // Navigate to about:blank first to ensure no page scripts are running after this point.
 | ||||||
|     await page?.mainFrame().goto(metadata, 'about:blank', { timeout: 0 }); |     await page?.mainFrame().goto(metadata, 'about:blank', { timeout: 0 }); | ||||||
|     page?.frameManager.setCloseAllOpeningDialogs(false); |  | ||||||
| 
 | 
 | ||||||
|     await this._resetStorage(); |     await this._resetStorage(); | ||||||
|     await this.clock.resetForReuse(); |     await this.clock.resetForReuse(); | ||||||
|  |  | ||||||
|  | @ -30,7 +30,6 @@ import { CRPage } from './crPage'; | ||||||
| import { saveProtocolStream } from './crProtocolHelper'; | import { saveProtocolStream } from './crProtocolHelper'; | ||||||
| import { CRServiceWorker } from './crServiceWorker'; | import { CRServiceWorker } from './crServiceWorker'; | ||||||
| 
 | 
 | ||||||
| import type { Dialog } from '../dialog'; |  | ||||||
| import type { InitScript, Worker } from '../page'; | import type { InitScript, Worker } from '../page'; | ||||||
| import type { ConnectionTransport } from '../transport'; | import type { ConnectionTransport } from '../transport'; | ||||||
| import type * as types from '../types'; | import type * as types from '../types'; | ||||||
|  | @ -498,12 +497,7 @@ export class CRBrowserContext extends BrowserContext { | ||||||
|     // dialogs, so we should close all that are currently opened.
 |     // dialogs, so we should close all that are currently opened.
 | ||||||
|     // We also won't get new ones since `Target.disposeBrowserContext` does not trigger
 |     // We also won't get new ones since `Target.disposeBrowserContext` does not trigger
 | ||||||
|     // beforeunload.
 |     // beforeunload.
 | ||||||
|     const openedBeforeUnloadDialogs: Dialog[] = []; |     await this.dialogManager.closeBeforeUnloadDialogs(); | ||||||
|     for (const crPage of this._crPages()) { |  | ||||||
|       const dialogs = [...crPage._page.frameManager._openedDialogs].filter(dialog => dialog.type() === 'beforeunload'); |  | ||||||
|       openedBeforeUnloadDialogs.push(...dialogs); |  | ||||||
|     } |  | ||||||
|     await Promise.all(openedBeforeUnloadDialogs.map(dialog => dialog.dismiss())); |  | ||||||
| 
 | 
 | ||||||
|     if (!this._browserContextId) { |     if (!this._browserContextId) { | ||||||
|       await this.stopVideoRecording(); |       await this.stopVideoRecording(); | ||||||
|  |  | ||||||
|  | @ -817,7 +817,7 @@ class FrameSession { | ||||||
|   _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) { |   _onDialog(event: Protocol.Page.javascriptDialogOpeningPayload) { | ||||||
|     if (!this._page.frameManager.frame(this._targetId)) |     if (!this._page.frameManager.frame(this._targetId)) | ||||||
|       return; // Our frame/subtree may be gone already.
 |       return; // Our frame/subtree may be gone already.
 | ||||||
|     this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( |     this._page.browserContext.dialogManager.dialogDidOpen(new dialog.Dialog( | ||||||
|         this._page, |         this._page, | ||||||
|         event.type, |         event.type, | ||||||
|         event.message, |         event.message, | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
| import { assert } from '../utils'; | import { assert } from '../utils'; | ||||||
| import { SdkObject } from './instrumentation'; | import { SdkObject } from './instrumentation'; | ||||||
| 
 | 
 | ||||||
|  | import type { Instrumentation } from './instrumentation'; | ||||||
| import type { Page } from './page'; | import type { Page } from './page'; | ||||||
| 
 | 
 | ||||||
| type OnHandle = (accept: boolean, promptText?: string) => Promise<void>; | type OnHandle = (accept: boolean, promptText?: string) => Promise<void>; | ||||||
|  | @ -39,8 +40,6 @@ 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(this); |  | ||||||
|     this.instrumentation.onDialog(this); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   page() { |   page() { | ||||||
|  | @ -62,14 +61,14 @@ export class Dialog extends SdkObject { | ||||||
|   async accept(promptText?: string) { |   async accept(promptText?: string) { | ||||||
|     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(this); |     this._page.browserContext.dialogManager.dialogWillClose(this); | ||||||
|     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(this); |     this._page.browserContext.dialogManager.dialogWillClose(this); | ||||||
|     await this._onHandle(false); |     await this._onHandle(false); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -80,3 +79,56 @@ export class Dialog extends SdkObject { | ||||||
|       await this.dismiss(); |       await this.dismiss(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export class DialogManager { | ||||||
|  |   private _instrumentation: Instrumentation; | ||||||
|  |   private _dialogHandlers = new Set<(dialog: Dialog) => boolean>(); | ||||||
|  |   private _openedDialogs = new Set<Dialog>(); | ||||||
|  | 
 | ||||||
|  |   constructor(instrumentation: Instrumentation) { | ||||||
|  |     this._instrumentation = instrumentation; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   dialogDidOpen(dialog: Dialog) { | ||||||
|  |     // Any ongoing evaluations will be stalled until the dialog is closed.
 | ||||||
|  |     for (const frame of dialog.page().frameManager.frames()) | ||||||
|  |       frame._invalidateNonStallingEvaluations('JavaScript dialog interrupted evaluation'); | ||||||
|  |     this._openedDialogs.add(dialog); | ||||||
|  |     this._instrumentation.onDialog(dialog); | ||||||
|  | 
 | ||||||
|  |     let hasHandlers = false; | ||||||
|  |     for (const handler of this._dialogHandlers) { | ||||||
|  |       if (handler(dialog)) | ||||||
|  |         hasHandlers = true; | ||||||
|  |     } | ||||||
|  |     if (!hasHandlers) | ||||||
|  |       dialog.close().then(() => {}); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   dialogWillClose(dialog: Dialog) { | ||||||
|  |     this._openedDialogs.delete(dialog); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addDialogHandler(handler: (dialog: Dialog) => boolean) { | ||||||
|  |     this._dialogHandlers.add(handler); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   removeDialogHandler(handler: (dialog: Dialog) => boolean) { | ||||||
|  |     this._dialogHandlers.delete(handler); | ||||||
|  |     if (!this._dialogHandlers.size) { | ||||||
|  |       for (const dialog of this._openedDialogs) | ||||||
|  |         dialog.close().catch(() => {}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   hasOpenDialogsForPage(page: Page) { | ||||||
|  |     return [...this._openedDialogs].some(dialog => dialog.page() === page); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async closeBeforeUnloadDialogs() { | ||||||
|  |     await Promise.all([...this._openedDialogs].map(async dialog => { | ||||||
|  |       if (dialog.type() === 'beforeunload') | ||||||
|  |         await dialog.dismiss(); | ||||||
|  |     })); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -53,6 +53,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel | ||||||
|   _webSocketInterceptionPatterns: channels.BrowserContextSetWebSocketInterceptionPatternsParams['patterns'] = []; |   _webSocketInterceptionPatterns: channels.BrowserContextSetWebSocketInterceptionPatternsParams['patterns'] = []; | ||||||
|   private _bindings: PageBinding[] = []; |   private _bindings: PageBinding[] = []; | ||||||
|   private _initScritps: InitScript[] = []; |   private _initScritps: InitScript[] = []; | ||||||
|  |   private _dialogHandler: (dialog: Dialog) => boolean; | ||||||
| 
 | 
 | ||||||
|   static from(parentScope: DispatcherScope, context: BrowserContext): BrowserContextDispatcher { |   static from(parentScope: DispatcherScope, context: BrowserContext): BrowserContextDispatcher { | ||||||
|     const result = parentScope.connection.existingDispatcher<BrowserContextDispatcher>(context); |     const result = parentScope.connection.existingDispatcher<BrowserContextDispatcher>(context); | ||||||
|  | @ -115,12 +116,13 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     this.addObjectListener(BrowserContext.Events.Dialog, (dialog: Dialog) => { |     this._dialogHandler = dialog => { | ||||||
|       if (this._shouldDispatchEvent(dialog.page(), 'dialog')) |       if (!this._shouldDispatchEvent(dialog.page(), 'dialog')) | ||||||
|         this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this, dialog) }); |         return false; | ||||||
|       else |       this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this, dialog) }); | ||||||
|         dialog.close().catch(() => {}); |       return true; | ||||||
|     }); |     }; | ||||||
|  |     context.dialogManager.addDialogHandler(this._dialogHandler); | ||||||
| 
 | 
 | ||||||
|     if (context._browser.options.name === 'chromium') { |     if (context._browser.options.name === 'chromium') { | ||||||
|       for (const page of (context as CRBrowserContext).backgroundPages()) |       for (const page of (context as CRBrowserContext).backgroundPages()) | ||||||
|  | @ -378,6 +380,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel | ||||||
|     // Avoid protocol calls for the closed context.
 |     // Avoid protocol calls for the closed context.
 | ||||||
|     if (this._context.isClosingOrClosed()) |     if (this._context.isClosingOrClosed()) | ||||||
|       return; |       return; | ||||||
|  |     this._context.dialogManager.removeDialogHandler(this._dialogHandler); | ||||||
|     this._context.setRequestInterceptor(undefined).catch(() => {}); |     this._context.setRequestInterceptor(undefined).catch(() => {}); | ||||||
|     this._context.removeExposedBindings(this._bindings).catch(() => {}); |     this._context.removeExposedBindings(this._bindings).catch(() => {}); | ||||||
|     this._bindings = []; |     this._bindings = []; | ||||||
|  |  | ||||||
|  | @ -237,7 +237,7 @@ export class FFPage implements PageDelegate { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onDialogOpened(params: Protocol.Page.dialogOpenedPayload) { |   _onDialogOpened(params: Protocol.Page.dialogOpenedPayload) { | ||||||
|     this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( |     this._page.browserContext.dialogManager.dialogDidOpen(new dialog.Dialog( | ||||||
|         this._page, |         this._page, | ||||||
|         params.type, |         params.type, | ||||||
|         params.message, |         params.message, | ||||||
|  |  | ||||||
|  | @ -36,7 +36,6 @@ import { ManualPromise } from '../utils/isomorphic/manualPromise'; | ||||||
| import { compressCallLog } from './callLog'; | import { compressCallLog } from './callLog'; | ||||||
| 
 | 
 | ||||||
| import type { ConsoleMessage } from './console'; | import type { ConsoleMessage } from './console'; | ||||||
| import type { Dialog } from './dialog'; |  | ||||||
| import type { ElementStateWithoutStable, FrameExpectParams, InjectedScript } from '@injected/injectedScript'; | import type { ElementStateWithoutStable, FrameExpectParams, InjectedScript } from '@injected/injectedScript'; | ||||||
| import type { CallMetadata } from './instrumentation'; | import type { CallMetadata } from './instrumentation'; | ||||||
| import type { Progress } from './progress'; | import type { Progress } from './progress'; | ||||||
|  | @ -101,8 +100,6 @@ export class FrameManager { | ||||||
|   readonly _consoleMessageTags = new Map<string, ConsoleTagHandler>(); |   readonly _consoleMessageTags = new Map<string, ConsoleTagHandler>(); | ||||||
|   readonly _signalBarriers = new Set<SignalBarrier>(); |   readonly _signalBarriers = new Set<SignalBarrier>(); | ||||||
|   private _webSockets = new Map<string, network.WebSocket>(); |   private _webSockets = new Map<string, network.WebSocket>(); | ||||||
|   _openedDialogs: Set<Dialog> = new Set(); |  | ||||||
|   private _closeAllOpeningDialogs = false; |  | ||||||
| 
 | 
 | ||||||
|   constructor(page: Page) { |   constructor(page: Page) { | ||||||
|     this._page = page; |     this._page = page; | ||||||
|  | @ -343,29 +340,6 @@ export class FrameManager { | ||||||
|     this._page.emitOnContext(BrowserContext.Events.RequestFailed, request); |     this._page.emitOnContext(BrowserContext.Events.RequestFailed, request); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   dialogDidOpen(dialog: Dialog) { |  | ||||||
|     // Any ongoing evaluations will be stalled until the dialog is closed.
 |  | ||||||
|     for (const frame of this._frames.values()) |  | ||||||
|       frame._invalidateNonStallingEvaluations('JavaScript dialog interrupted evaluation'); |  | ||||||
|     if (this._closeAllOpeningDialogs) |  | ||||||
|       dialog.close().then(() => {}); |  | ||||||
|     else |  | ||||||
|       this._openedDialogs.add(dialog); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   dialogWillClose(dialog: Dialog) { |  | ||||||
|     this._openedDialogs.delete(dialog); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async closeOpenDialogs() { |  | ||||||
|     await Promise.all([...this._openedDialogs].map(dialog => dialog.close())).catch(() => {}); |  | ||||||
|     this._openedDialogs.clear(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   setCloseAllOpeningDialogs(closeDialogs: boolean) { |  | ||||||
|     this._closeAllOpeningDialogs = closeDialogs; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   removeChildFramesRecursively(frame: Frame) { |   removeChildFramesRecursively(frame: Frame) { | ||||||
|     for (const child of frame.childFrames()) |     for (const child of frame.childFrames()) | ||||||
|       this._removeFramesRecursively(child); |       this._removeFramesRecursively(child); | ||||||
|  | @ -563,7 +537,7 @@ export class Frame extends SdkObject { | ||||||
|   async raceAgainstEvaluationStallingEvents<T>(cb: () => Promise<T>): Promise<T> { |   async raceAgainstEvaluationStallingEvents<T>(cb: () => Promise<T>): Promise<T> { | ||||||
|     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._openedDialogs.size) |     if (this._page.browserContext.dialogManager.hasOpenDialogsForPage(this._page)) | ||||||
|       throw new Error('Open JavaScript dialog prevents evaluation'); |       throw new Error('Open JavaScript dialog prevents evaluation'); | ||||||
| 
 | 
 | ||||||
|     const promise = new ManualPromise<T>(); |     const promise = new ManualPromise<T>(); | ||||||
|  |  | ||||||
|  | @ -29,7 +29,6 @@ import { generateCode } from '../codegen/language'; | ||||||
| 
 | 
 | ||||||
| import type { RegisteredListener } from '../../utils'; | import type { RegisteredListener } from '../../utils'; | ||||||
| import type { Language, LanguageGenerator, LanguageGeneratorOptions } from '../codegen/types'; | import type { Language, LanguageGenerator, LanguageGeneratorOptions } from '../codegen/types'; | ||||||
| import type { Dialog } from '../dialog'; |  | ||||||
| import type * as channels from '@protocol/channels'; | import type * as channels from '@protocol/channels'; | ||||||
| import type * as actions from '@recorder/actions'; | import type * as actions from '@recorder/actions'; | ||||||
| import type { Source } from '@recorder/recorderTypes'; | import type { Source } from '@recorder/recorderTypes'; | ||||||
|  | @ -135,7 +134,11 @@ export class ContextRecorder extends EventEmitter { | ||||||
|     this._context.on(BrowserContext.Events.Page, (page: Page) => this._onPage(page)); |     this._context.on(BrowserContext.Events.Page, (page: Page) => this._onPage(page)); | ||||||
|     for (const page of this._context.pages()) |     for (const page of this._context.pages()) | ||||||
|       this._onPage(page); |       this._onPage(page); | ||||||
|     this._context.on(BrowserContext.Events.Dialog, (dialog: Dialog) => this._onDialog(dialog.page())); |     this._context.dialogManager.addDialogHandler(dialog => { | ||||||
|  |       this._onDialog(dialog.page()); | ||||||
|  |       // Not handling the dialog, let it automatically close.
 | ||||||
|  |       return false; | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     // Input actions that potentially lead to navigation are intercepted on the page and are
 |     // Input actions that potentially lead to navigation are intercepted on the page and are
 | ||||||
|     // performed by the Playwright.
 |     // performed by the Playwright.
 | ||||||
|  |  | ||||||
|  | @ -593,7 +593,7 @@ export class WKPage implements PageDelegate { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) { |   _onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) { | ||||||
|     this._page.emitOnContext(BrowserContext.Events.Dialog, new dialog.Dialog( |     this._page.browserContext.dialogManager.dialogDidOpen(new dialog.Dialog( | ||||||
|         this._page, |         this._page, | ||||||
|         event.type as dialog.DialogType, |         event.type as dialog.DialogType, | ||||||
|         event.message, |         event.message, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue