@@ -32,6 +32,10 @@ import type { SubmittedAnnotationFrame, Tab } from '@dashboard/dashboardChannel'
3232import type { BrowserDescriptor } from '../../serverRegistry' ;
3333import type { SessionProvider } from './sessionProvider' ;
3434
35+ export type AnnotateResult =
36+ | { type : 'submitted' , frames : SubmittedAnnotationFrame [ ] , feedback : string }
37+ | { type : 'cancelled' } ;
38+
3539export class DashboardConnection implements Transport {
3640 sendEvent ?: ( method : string , params : any ) => void ;
3741 close ?: ( ) => void ;
@@ -40,20 +44,18 @@ export class DashboardConnection implements Transport {
4044 private _attachedPage : AttachedPage | undefined ;
4145 private _onclose : ( ) => void ;
4246 private _onconnected ?: ( ) => void ;
43- private _onAnnotationSubmit ?: ( frames : SubmittedAnnotationFrame [ ] , feedback : string ) => void ;
4447 private _pushTabsScheduled = false ;
4548 private _visible = true ;
4649 private _pendingReveal : { sessionName ?: string ; workspaceDir ?: string ; pageId ?: string ; done : ManualPromise < void > } | undefined ;
47- private _annotateWaitingForAttach = false ;
50+ private _pendingAnnotate : { resolve : ( result : AnnotateResult ) => void ; dispose : ( ) => void } | undefined ;
4851
4952 _recordingDir : string ;
5053 _streams = new Map < string , { handle : fs . promises . FileHandle ; path : string } > ( ) ;
5154
52- constructor ( provider : SessionProvider , onclose : ( ) => void , onconnected ?: ( ) => void , onAnnotationSubmit ?: ( frames : SubmittedAnnotationFrame [ ] , feedback : string ) => void ) {
55+ constructor ( provider : SessionProvider , onclose : ( ) => void , onconnected ?: ( ) => void ) {
5356 this . _provider = provider ;
5457 this . _onclose = onclose ;
5558 this . _onconnected = onconnected ;
56- this . _onAnnotationSubmit = onAnnotationSubmit ;
5759 this . _recordingDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'playwright-recordings-' ) ) ;
5860 }
5961
@@ -84,6 +86,7 @@ export class DashboardConnection implements Transport {
8486 // Reject any in-flight reveal so callers awaiting it don't hang.
8587 this . _pendingReveal ?. done . reject ( new Error ( 'Dashboard connection closed' ) ) ;
8688 this . _pendingReveal = undefined ;
89+ this . _resolvePendingAnnotate ( { type : 'cancelled' } ) ;
8790 for ( const stream of this . _streams . values ( ) ) {
8891 void stream . handle . close ( )
8992 . catch ( ( ) => { } )
@@ -193,11 +196,11 @@ export class DashboardConnection implements Transport {
193196 }
194197
195198 async submitAnnotation ( params : { frames : SubmittedAnnotationFrame [ ] ; feedback : string } ) {
196- this . _onAnnotationSubmit ?. ( params . frames , params . feedback ) ;
199+ this . _resolvePendingAnnotate ( { type : 'submitted' , frames : params . frames , feedback : params . feedback } ) ;
197200 }
198201
199202 async cancelAnnotation ( ) {
200- this . _onAnnotationSubmit ?. ( [ ] , '' ) ;
203+ this . _resolvePendingAnnotate ( { type : 'cancelled' } ) ;
201204 }
202205
203206 async reveal ( params : { path : string } ) {
@@ -245,18 +248,43 @@ export class DashboardConnection implements Transport {
245248 this . sendEvent ?.( 'frame' , { data, viewportWidth, viewportHeight } ) ;
246249 }
247250
248- emitAnnotate ( ) {
251+ emitAnnotate ( { signal } : { signal : AbortSignal } ) : Promise < AnnotateResult > {
252+ return new Promise < AnnotateResult > ( resolve => {
253+ if ( signal . aborted ) {
254+ resolve ( { type : 'cancelled' } ) ;
255+ return ;
256+ }
257+ // Latest emitAnnotate supersedes any in-flight one on the same connection.
258+ this . _resolvePendingAnnotate ( { type : 'cancelled' } ) ;
259+ const onAbort = ( ) => {
260+ if ( this . _pendingAnnotate !== pending )
261+ return ;
262+ this . _pendingAnnotate = undefined ;
263+ pending . dispose ( ) ;
264+ this . sendEvent ?.( 'cancelAnnotate' , { } ) ;
265+ resolve ( { type : 'cancelled' } ) ;
266+ } ;
267+ const pending : NonNullable < typeof this . _pendingAnnotate > = {
268+ resolve,
269+ dispose : ( ) => signal . removeEventListener ( 'abort' , onAbort ) ,
270+ } ;
271+ this . _pendingAnnotate = pending ;
272+ signal . addEventListener ( 'abort' , onAbort ) ;
273+ this . _tryFireAnnotate ( ) ;
274+ } ) ;
275+ }
276+
277+ private _tryFireAnnotate ( ) {
249278 // Defer until a page is attached so the client can fetch a screenshot.
250- if ( ! this . _attachedPage ) {
251- this . _annotateWaitingForAttach = true ;
279+ if ( ! this . _pendingAnnotate || ! this . _attachedPage )
252280 return ;
253- }
254281 this . sendEvent ?.( 'annotate' , { } ) ;
255282 }
256283
257- emitCancelAnnotate ( ) {
258- this . _annotateWaitingForAttach = false ;
259- this . sendEvent ?.( 'cancelAnnotate' , { } ) ;
284+ private _resolvePendingAnnotate ( result : AnnotateResult ) {
285+ this . _pendingAnnotate ?. resolve ( result ) ;
286+ this . _pendingAnnotate ?. dispose ( ) ;
287+ this . _pendingAnnotate = undefined ;
260288 }
261289
262290 artifactsDirFor ( context : api . BrowserContext ) : string {
@@ -326,10 +354,8 @@ export class DashboardConnection implements Transport {
326354 attached . dispose ( ) ;
327355 throw e ;
328356 }
329- if ( this . _annotateWaitingForAttach && this . _attachedPage === attached ) {
330- this . _annotateWaitingForAttach = false ;
331- this . sendEvent ?.( 'annotate' , { } ) ;
332- }
357+ if ( this . _attachedPage === attached )
358+ this . _tryFireAnnotate ( ) ;
333359 }
334360
335361 _handleAttachedPageClose ( context : api . BrowserContext ) {
0 commit comments