@@ -144,6 +144,7 @@ export class Page extends SdkObject {
144144 _pageIsError : Error | undefined ;
145145 _video : Artifact | null = null ;
146146 _opener : Page | undefined ;
147+ private _frameThrottler = new FrameThrottler ( 10 , 200 ) ;
147148
148149 constructor ( delegate : PageDelegate , browserContext : BrowserContext ) {
149150 super ( browserContext , 'page' ) ;
@@ -209,6 +210,7 @@ export class Page extends SdkObject {
209210
210211 _didClose ( ) {
211212 this . _frameManager . dispose ( ) ;
213+ this . _frameThrottler . setEnabled ( false ) ;
212214 assert ( this . _closedState !== 'closed' , 'Page closed twice' ) ;
213215 this . _closedState = 'closed' ;
214216 this . emit ( Page . Events . Close ) ;
@@ -217,12 +219,14 @@ export class Page extends SdkObject {
217219
218220 _didCrash ( ) {
219221 this . _frameManager . dispose ( ) ;
222+ this . _frameThrottler . setEnabled ( false ) ;
220223 this . emit ( Page . Events . Crash ) ;
221224 this . _crashedPromise . resolve ( new Error ( 'Page crashed' ) ) ;
222225 }
223226
224227 _didDisconnect ( ) {
225228 this . _frameManager . dispose ( ) ;
229+ this . _frameThrottler . setEnabled ( false ) ;
226230 assert ( ! this . _disconnected , 'Page disconnected twice' ) ;
227231 this . _disconnected = true ;
228232 this . _disconnectedPromise . resolve ( new Error ( 'Page closed' ) ) ;
@@ -495,6 +499,16 @@ export class Page extends SdkObject {
495499
496500 setScreencastOptions ( options : { width : number , height : number , quality : number } | null ) {
497501 this . _delegate . setScreencastOptions ( options ) . catch ( e => debugLogger . log ( 'error' , e ) ) ;
502+ this . _frameThrottler . setEnabled ( ! ! options ) ;
503+ }
504+
505+ throttleScreencastFrameAck ( ack : ( ) => void ) {
506+ // Don't ack immediately, tracing has smart throttling logic that is implemented here.
507+ this . _frameThrottler . ack ( ack ) ;
508+ }
509+
510+ temporarlyDisableTracingScreencastThrottling ( ) {
511+ this . _frameThrottler . recharge ( ) ;
498512 }
499513
500514 firePageError ( error : Error ) {
@@ -631,3 +645,57 @@ function addPageBinding(bindingName: string, needsHandle: boolean) {
631645 } ;
632646 ( globalThis as any ) [ bindingName ] . __installed = true ;
633647}
648+
649+ class FrameThrottler {
650+ private _acks : ( ( ) => void ) [ ] = [ ] ;
651+ private _interval : number ;
652+ private _nonThrottledFrames : number ;
653+ private _budget : number ;
654+ private _intervalId : NodeJS . Timeout | undefined ;
655+
656+ constructor ( nonThrottledFrames : number , interval : number ) {
657+ this . _nonThrottledFrames = nonThrottledFrames ;
658+ this . _budget = nonThrottledFrames ;
659+ this . _interval = interval ;
660+ }
661+
662+ setEnabled ( enabled : boolean ) {
663+ if ( enabled ) {
664+ if ( this . _intervalId )
665+ clearInterval ( this . _intervalId ) ;
666+ this . _intervalId = setInterval ( ( ) => this . _tick ( ) , this . _interval ) ;
667+ } else if ( this . _intervalId ) {
668+ clearInterval ( this . _intervalId ) ;
669+ this . _intervalId = undefined ;
670+ }
671+ }
672+
673+ recharge ( ) {
674+ // Send all acks, reset budget.
675+ for ( const ack of this . _acks )
676+ ack ( ) ;
677+ this . _acks = [ ] ;
678+ this . _budget = this . _nonThrottledFrames ;
679+ }
680+
681+ ack ( ack : ( ) => void ) {
682+ // Either not engaged or video is also recording, don't throttle.
683+ if ( ! this . _intervalId ) {
684+ ack ( ) ;
685+ return ;
686+ }
687+
688+ // Do we have enough budget to respond w/o throttling?
689+ if ( -- this . _budget > 0 ) {
690+ ack ( ) ;
691+ return ;
692+ }
693+
694+ // Schedule.
695+ this . _acks . push ( ack ) ;
696+ }
697+
698+ private _tick ( ) {
699+ this . _acks . shift ( ) ?.( ) ;
700+ }
701+ }
0 commit comments