@@ -213,19 +213,27 @@ export function refused(status: number, method: string, path: string) {
213213 return new RegExp ( `^http \\S+ ${ status } ${ method } ${ escapeRegExp ( path ) } ${ query } $` )
214214}
215215
216+ /** What a page paints with rather than reads. An SVG `<use href>` reports its type as `other`. */
217+ const ASSET_RESOURCE = new Set ( [ 'image' , 'font' , 'media' ] )
218+ const STATIC_ASSET_PATH = / ^ \/ (? ! a p i \/ ) .* \. (?: s v g | p n g | j p e ? g | w e b p | a v i f | g i f | i c o | w o f f 2 ? | t t f ) $ /
219+
216220/**
217- * A read this page is waiting on. Router prefetches, static chunks and other hosts come and go on
221+ * A read this page is waiting on. Router prefetches, static assets and other hosts come and go on
218222 * their own schedule, and no check reasons about them.
219223 */
220224function isPageRead ( request : Request ) {
221225 if ( request . resourceType ( ) === 'eventsource' || ! isSameOrigin ( request . url ( ) ) ) return false
222- if ( new URL ( request . url ( ) ) . pathname . startsWith ( '/_next/static/' ) ) return false
226+ if ( ASSET_RESOURCE . has ( request . resourceType ( ) ) ) return false
227+ const { pathname } = new URL ( request . url ( ) )
228+ if ( pathname . startsWith ( '/_next/static/' ) || STATIC_ASSET_PATH . test ( pathname ) ) return false
223229 return request . headers ( ) [ 'next-router-prefetch' ] === undefined
224230}
225231
226232class Session {
227233 private readonly inflight = new Set < Request > ( )
228234 private lastActivity = Date . now ( )
235+ /** The document a navigation is loading, until it replaces the page. */
236+ private loadingDocument : Request | null = null
229237 readonly requests : { method : string ; path : string ; type : string } [ ] = [ ]
230238
231239 constructor (
@@ -249,9 +257,8 @@ class Session {
249257 } )
250258 page . on ( 'request' , ( request ) => {
251259 if ( HIGH_VOLUME_REQUEST . test ( request . url ( ) ) ) return
252- // A navigation does not report every request it cancels, and none of them still matters.
253260 if ( request . isNavigationRequest ( ) && request . frame ( ) === page . mainFrame ( ) )
254- this . inflight . clear ( )
261+ this . loadingDocument = request
255262 if ( isPageRead ( request ) ) {
256263 this . inflight . add ( request )
257264 this . lastActivity = Date . now ( )
@@ -263,12 +270,21 @@ class Session {
263270 type : request . resourceType ( ) ,
264271 } )
265272 } )
273+ // The browser never reports the end of a read the old document was running or sent while this
274+ // one loaded, including after the request that started the navigation.
275+ page . on ( 'framenavigated' , ( frame ) => {
276+ if ( frame !== page . mainFrame ( ) || ! this . loadingDocument ) return
277+ for ( const request of this . inflight )
278+ if ( request !== this . loadingDocument ) this . inflight . delete ( request )
279+ this . loadingDocument = null
280+ } )
266281 const settled = ( request : Request ) => {
267282 if ( this . inflight . delete ( request ) ) this . lastActivity = Date . now ( )
268283 }
269284 page . on ( 'requestfinished' , settled )
270285 page . on ( 'requestfailed' , ( request ) => {
271286 settled ( request )
287+ if ( request === this . loadingDocument ) this . loadingDocument = null
272288 const reason = request . failure ( ) ?. errorText ?? 'failed'
273289 if (
274290 isSameOrigin ( request . url ( ) ) &&
0 commit comments