2021-10-11 19:52:28 -08:00
/ * *
* Copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2022-09-20 18:41:51 -07:00
import { MultiMap } from './multimap' ;
2023-01-30 19:07:52 -08:00
import { unwrapPopoutUrl } from './snapshotRenderer' ;
2021-10-12 13:42:50 -08:00
import { SnapshotServer } from './snapshotServer' ;
import { TraceModel } from './traceModel' ;
2021-10-11 19:52:28 -08:00
// @ts-ignore
declare const self : ServiceWorkerGlobalScope ;
2021-10-13 10:07:29 -08:00
self . addEventListener ( 'install' , function ( event : any ) {
self . skipWaiting ( ) ;
} ) ;
2021-10-11 19:52:28 -08:00
self . addEventListener ( 'activate' , function ( event : any ) {
event . waitUntil ( self . clients . claim ( ) ) ;
} ) ;
2021-10-13 10:07:29 -08:00
const scopePath = new URL ( self . registration . scope ) . pathname ;
2021-10-11 19:52:28 -08:00
2022-04-02 03:20:05 +08:00
const loadedTraces = new Map < string , { traceModel : TraceModel , snapshotServer : SnapshotServer } > ( ) ;
2022-08-15 20:54:57 -07:00
const clientIdToTraceUrls = new MultiMap < string , string > ( ) ;
2021-10-13 12:31:54 -08:00
2022-09-26 20:57:05 +02:00
async function loadTrace ( traceUrl : string , traceFileName : string | null , clientId : string , progress : ( done : number , total : number ) = > void ) : Promise < TraceModel > {
const entry = loadedTraces . get ( traceUrl ) ;
clientIdToTraceUrls . set ( clientId , traceUrl ) ;
2021-10-13 12:31:54 -08:00
if ( entry )
return entry . traceModel ;
2021-10-12 13:42:50 -08:00
const traceModel = new TraceModel ( ) ;
2022-09-26 20:57:05 +02:00
try {
await traceModel . load ( traceUrl , progress ) ;
} catch ( error : any ) {
// eslint-disable-next-line no-console
console . error ( error ) ;
if ( error ? . message ? . includes ( 'Cannot find .trace file' ) && await traceModel . hasEntry ( 'index.html' ) )
throw new Error ( 'Could not load trace. Did you upload a Playwright HTML report instead? Make sure to extract the archive first and then double-click the index.html file or put it on a web server.' ) ;
else if ( traceFileName )
throw new Error ( ` Could not load trace from ${ traceFileName } . Make sure to upload a valid Playwright trace. ` ) ;
else
throw new Error ( ` Could not load trace from ${ traceUrl } . Make sure a valid Playwright Trace is accessible over this url. ` ) ;
}
2021-10-13 12:31:54 -08:00
const snapshotServer = new SnapshotServer ( traceModel . storage ( ) ) ;
2022-09-26 20:57:05 +02:00
loadedTraces . set ( traceUrl , { traceModel , snapshotServer } ) ;
2021-10-12 13:42:50 -08:00
return traceModel ;
2021-10-11 19:52:28 -08:00
}
2021-10-12 13:42:50 -08:00
// @ts-ignore
async function doFetch ( event : FetchEvent ) : Promise < Response > {
2021-10-11 19:52:28 -08:00
const request = event . request ;
2021-10-22 15:59:17 -08:00
const client = await self . clients . get ( event . clientId ) ;
2021-10-11 19:52:28 -08:00
2021-10-13 10:07:29 -08:00
if ( request . url . startsWith ( self . registration . scope ) ) {
2023-01-30 19:07:52 -08:00
const url = new URL ( unwrapPopoutUrl ( request . url ) ) ;
2021-10-13 10:07:29 -08:00
const relativePath = url . pathname . substring ( scopePath . length - 1 ) ;
2021-10-22 14:14:58 -08:00
if ( relativePath === '/ping' ) {
2021-10-13 12:31:54 -08:00
await gc ( ) ;
2021-10-22 14:14:58 -08:00
return new Response ( null , { status : 200 } ) ;
}
2021-11-11 21:31:19 +01:00
const traceUrl = url . searchParams . get ( 'trace' ) ! ;
2021-11-09 15:12:37 -08:00
const { snapshotServer } = loadedTraces . get ( traceUrl ) || { } ;
2023-02-27 22:31:47 -08:00
if ( relativePath === '/contexts' ) {
2021-11-11 21:31:19 +01:00
try {
2022-09-26 20:57:05 +02:00
const traceModel = await loadTrace ( traceUrl , url . searchParams . get ( 'traceFileName' ) , event . clientId , ( done : number , total : number ) = > {
2021-11-11 21:31:19 +01:00
client . postMessage ( { method : 'progress' , params : { done , total } } ) ;
} ) ;
2023-02-27 22:31:47 -08:00
return new Response ( JSON . stringify ( traceModel ! . contextEntries ) , {
2021-11-11 21:31:19 +01:00
status : 200 ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
2022-09-26 20:57:05 +02:00
} catch ( error : any ) {
return new Response ( JSON . stringify ( { error : error?.message } ) , {
2021-11-11 21:31:19 +01:00
status : 500 ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
}
2021-10-12 13:42:50 -08:00
}
2021-10-13 12:31:54 -08:00
2021-11-02 16:35:23 -08:00
if ( relativePath . startsWith ( '/snapshotInfo/' ) ) {
2021-10-13 12:31:54 -08:00
if ( ! snapshotServer )
2021-10-12 13:42:50 -08:00
return new Response ( null , { status : 404 } ) ;
2021-11-02 16:35:23 -08:00
return snapshotServer . serveSnapshotInfo ( relativePath , url . searchParams ) ;
2021-10-12 13:42:50 -08:00
}
2021-10-13 12:31:54 -08:00
if ( relativePath . startsWith ( '/snapshot/' ) ) {
if ( ! snapshotServer )
return new Response ( null , { status : 404 } ) ;
2023-01-30 19:07:52 -08:00
return snapshotServer . serveSnapshot ( relativePath , url . searchParams , url . href ) ;
2021-10-13 12:31:54 -08:00
}
if ( relativePath . startsWith ( '/sha1/' ) ) {
// Sha1 is unique, load it from either of the models for simplicity.
for ( const { traceModel } of loadedTraces . values ( ) ) {
const blob = await traceModel ! . resourceForSha1 ( relativePath . slice ( '/sha1/' . length ) ) ;
if ( blob )
return new Response ( blob , { status : 200 } ) ;
}
return new Response ( null , { status : 404 } ) ;
}
// Fallback to network.
2021-10-12 13:42:50 -08:00
return fetch ( event . request ) ;
2021-10-11 19:52:28 -08:00
}
2023-01-30 19:07:52 -08:00
const snapshotUrl = unwrapPopoutUrl ( client ! . url ) ;
2021-11-09 15:12:37 -08:00
const traceUrl = new URL ( snapshotUrl ) . searchParams . get ( 'trace' ) ! ;
const { snapshotServer } = loadedTraces . get ( traceUrl ) || { } ;
2021-10-12 13:42:50 -08:00
if ( ! snapshotServer )
return new Response ( null , { status : 404 } ) ;
2023-02-28 17:08:46 -08:00
const lookupUrls = [ request . url ] ;
// When trace viewer is deployed over https, Chrome changes http subresources
// in snapshots to https, presumably to avoid mixed-content.
// In this case, we additionally match http resources from the archive.
if ( self . registration . scope . startsWith ( 'https://' ) && request . url . startsWith ( 'https://' ) )
lookupUrls . push ( request . url . replace ( /^https/ , 'http' ) ) ;
return snapshotServer . serveResource ( lookupUrls , snapshotUrl ) ;
2021-10-13 12:31:54 -08:00
}
async function gc() {
2022-04-02 03:20:05 +08:00
const clients = await self . clients . matchAll ( ) ;
2021-10-13 12:31:54 -08:00
const usedTraces = new Set < string > ( ) ;
2022-04-02 03:20:05 +08:00
2022-08-15 20:54:57 -07:00
for ( const [ clientId , traceUrls ] of clientIdToTraceUrls ) {
2022-04-02 03:20:05 +08:00
// @ts-ignore
if ( ! clients . find ( c = > c . id === clientId ) )
2022-08-15 20:54:57 -07:00
clientIdToTraceUrls . deleteAll ( clientId ) ;
2022-04-02 03:20:05 +08:00
else
2022-08-15 20:54:57 -07:00
traceUrls . forEach ( url = > usedTraces . add ( url ) ) ;
2021-10-13 12:31:54 -08:00
}
for ( const traceUrl of loadedTraces . keys ( ) ) {
if ( ! usedTraces . has ( traceUrl ) )
loadedTraces . delete ( traceUrl ) ;
}
2021-10-11 19:52:28 -08:00
}
2021-10-12 13:42:50 -08:00
// @ts-ignore
self . addEventListener ( 'fetch' , function ( event : FetchEvent ) {
2021-10-11 19:52:28 -08:00
event . respondWith ( doFetch ( event ) ) ;
} ) ;