@@ -145,6 +145,13 @@ function isBrowserWaitElementState(value: string): value is BrowserWaitElementSt
145145
146146type PageExecutionTarget = WebContents | WebFrameMain
147147
148+ /**
149+ * Refs remembered per page or frame. Above the page's own 5000-ref store by
150+ * one snapshot's 300 refs, and refreshed by the same snapshots, so the driver
151+ * never forgets a ref the page can still hand back.
152+ */
153+ const ISSUED_REFS_PER_TARGET = 5300
154+
148155/** Whether two targets are the same page or frame; Electron may hand out separate wrappers for one frame. */
149156function samePageTarget (
150157 left : PageExecutionTarget | undefined ,
@@ -340,6 +347,8 @@ interface DriverScopeState {
340347 nextElementRefId : number
341348 /** Every ref this scope issued and its frame: a later snapshot may reuse it there, and an action may still name it. */
342349 issuedRefTargets : Map < number , PageExecutionTarget >
350+ /** The same refs per page or frame, oldest first, so each page's history stays bounded. */
351+ issuedRefsByTarget : Map < PageExecutionTarget , Set < number > >
343352 /** Invalidates captures that finish after a tab/navigation/timeout race. */
344353 snapshotCaptureEpoch : number
345354 /** Cancels a timed-out multi-step action before any later native dispatch. */
@@ -382,6 +391,7 @@ function createDriverScopeState(): DriverScopeState {
382391 snapshotTargets : new Map ( ) ,
383392 nextElementRefId : 0 ,
384393 issuedRefTargets : new Map ( ) ,
394+ issuedRefsByTarget : new Map ( ) ,
385395 snapshotCaptureEpoch : 0 ,
386396 toolExecutionEpoch : 0 ,
387397 }
@@ -1781,6 +1791,25 @@ function requireSnapshotForElementAction(): void {
17811791 )
17821792}
17831793
1794+ function rememberIssuedRef (
1795+ state : DriverScopeState ,
1796+ ref : number ,
1797+ target : PageExecutionTarget
1798+ ) : void {
1799+ const previous = state . issuedRefTargets . get ( ref )
1800+ if ( previous && previous !== target ) state . issuedRefsByTarget . get ( previous ) ?. delete ( ref )
1801+ state . issuedRefTargets . set ( ref , target )
1802+ const refs = state . issuedRefsByTarget . get ( target ) ?? new Set < number > ( )
1803+ state . issuedRefsByTarget . set ( target , refs )
1804+ refs . delete ( ref )
1805+ refs . add ( ref )
1806+ const oldest = refs . values ( ) . next ( )
1807+ if ( refs . size > ISSUED_REFS_PER_TARGET && ! oldest . done ) {
1808+ refs . delete ( oldest . value )
1809+ state . issuedRefTargets . delete ( oldest . value )
1810+ }
1811+ }
1812+
17841813/**
17851814 * The frame that owns a ref: the latest snapshot's, or for a ref an earlier
17861815 * snapshot issued, its frame while that frame still belongs to the tab the
@@ -2604,12 +2633,12 @@ async function captureSnapshot(
26042633 ( targetLineIndexes . get ( elementId ) ?? Number . POSITIVE_INFINITY ) < visibleOutlineLines . length
26052634 )
26062635 )
2607- // Never forget a ref a live page may still hand back (the page itself
2608- // decides when to reissue); only refs of closed tabs and frames go.
2609- for ( const [ ref , target ] of state . issuedRefTargets ) {
2610- if ( 'isDestroyed' in target && target . isDestroyed ( ) ) state . issuedRefTargets . delete ( ref )
2636+ for ( const [ target , refs ] of state . issuedRefsByTarget ) {
2637+ if ( ! ( 'isDestroyed' in target && target . isDestroyed ( ) ) ) continue
2638+ for ( const ref of refs ) state . issuedRefTargets . delete ( ref )
2639+ state . issuedRefsByTarget . delete ( target )
26112640 }
2612- for ( const [ ref , target ] of targets ) state . issuedRefTargets . set ( ref , target )
2641+ for ( const [ ref , target ] of targets ) rememberIssuedRef ( state , ref , target )
26132642 state . snapshotTabId = capturedTabId
26142643 return {
26152644 ...omit ( mainSnapshot , [ 'refIds' , 'refLineIndexes' , 'nextElementId' ] ) ,
0 commit comments