2021-10-12 05:52:28 +02: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 .
* /
2023-05-06 00:12:18 +02:00
import { splitProgress } from './progress' ;
2023-01-31 04:07:52 +01:00
import { unwrapPopoutUrl } from './snapshotRenderer' ;
2021-10-12 23:42:50 +02:00
import { SnapshotServer } from './snapshotServer' ;
import { TraceModel } from './traceModel' ;
2024-11-14 15:27:33 +01:00
import { FetchTraceModelBackend , TraceViewerServer , ZipTraceModelBackend } from './traceModelBackends' ;
2024-07-22 17:16:25 +02:00
import { TraceVersionError } from './traceModernizer' ;
2021-10-12 05:52:28 +02:00
// @ts-ignore
declare const self : ServiceWorkerGlobalScope ;
2021-10-13 20:07:29 +02:00
self . addEventListener ( 'install' , function ( event : any ) {
self . skipWaiting ( ) ;
} ) ;
2021-10-12 05:52:28 +02:00
self . addEventListener ( 'activate' , function ( event : any ) {
event . waitUntil ( self . clients . claim ( ) ) ;
} ) ;
2021-10-13 20:07:29 +02:00
const scopePath = new URL ( self . registration . scope ) . pathname ;
2021-10-12 05:52:28 +02:00
2022-04-01 21:20:05 +02:00
const loadedTraces = new Map < string , { traceModel : TraceModel , snapshotServer : SnapshotServer } > ( ) ;
2024-11-14 15:27:33 +01:00
const clientIdToTraceUrls = new Map < string , { limit : number | undefined , traceUrls : Set < string > , traceViewerServer : TraceViewerServer } > ( ) ;
2021-10-13 22:31:54 +02:00
2024-11-14 15:27:33 +01:00
async function loadTrace ( traceUrl : string , traceFileName : string | null , client : any | undefined , limit : number | undefined , progress : ( done : number , total : number ) = > undefined ) : Promise < TraceModel > {
2023-08-25 21:10:28 +02:00
await gc ( ) ;
2024-11-14 15:27:33 +01:00
const clientId = client ? . id ? ? '' ;
2024-10-23 19:25:04 +02:00
let data = clientIdToTraceUrls . get ( clientId ) ;
if ( ! data ) {
2024-12-11 13:25:52 +01:00
const clientURL = new URL ( client ? . url ? ? self . registration . scope ) ;
const traceViewerServerBaseUrl = new URL ( clientURL . searchParams . get ( 'server' ) ? ? '../' , clientURL ) ;
2024-11-14 15:27:33 +01:00
data = { limit , traceUrls : new Set ( ) , traceViewerServer : new TraceViewerServer ( traceViewerServerBaseUrl ) } ;
2024-10-23 19:25:04 +02:00
clientIdToTraceUrls . set ( clientId , data ) ;
2023-08-25 21:10:28 +02:00
}
2024-10-23 19:25:04 +02:00
data . traceUrls . add ( traceUrl ) ;
2023-08-25 21:10:28 +02:00
2021-10-12 23:42:50 +02:00
const traceModel = new TraceModel ( ) ;
2022-09-26 20:57:05 +02:00
try {
2023-05-06 00:12:18 +02:00
// Allow 10% to hop from sw to page.
const [ fetchProgress , unzipProgress ] = splitProgress ( progress , [ 0.5 , 0.4 , 0.1 ] ) ;
2024-11-14 15:27:33 +01:00
const backend = traceUrl . endsWith ( 'json' ) ? new FetchTraceModelBackend ( traceUrl , data . traceViewerServer ) : new ZipTraceModelBackend ( traceUrl , data . traceViewerServer , fetchProgress ) ;
2024-09-27 01:46:27 +02:00
await traceModel . load ( backend , unzipProgress ) ;
2022-09-26 20:57:05 +02:00
} catch ( error : any ) {
2023-06-26 18:13:40 +02:00
// eslint-disable-next-line no-console
console . error ( error ) ;
2022-09-26 20:57:05 +02:00
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.' ) ;
2024-07-22 17:16:25 +02:00
if ( error instanceof TraceVersionError )
throw new Error ( ` Could not load trace from ${ traceFileName || traceUrl } . ${ error . message } ` ) ;
2023-03-23 20:49:53 +01:00
if ( traceFileName )
2022-09-26 20:57:05 +02:00
throw new Error ( ` Could not load trace from ${ traceFileName } . Make sure to upload a valid Playwright trace. ` ) ;
2023-03-23 20:49:53 +01:00
throw new Error ( ` Could not load trace from ${ traceUrl } . Make sure a valid Playwright Trace is accessible over this url. ` ) ;
2022-09-26 20:57:05 +02:00
}
2023-03-22 17:32:21 +01:00
const snapshotServer = new SnapshotServer ( traceModel . storage ( ) , sha1 = > traceModel . resourceForSha1 ( sha1 ) ) ;
2022-09-26 20:57:05 +02:00
loadedTraces . set ( traceUrl , { traceModel , snapshotServer } ) ;
2021-10-12 23:42:50 +02:00
return traceModel ;
2021-10-12 05:52:28 +02:00
}
2021-10-12 23:42:50 +02:00
// @ts-ignore
async function doFetch ( event : FetchEvent ) : Promise < Response > {
2024-03-15 16:27:07 +01:00
// In order to make Accessibility Insights for Web work.
if ( event . request . url . startsWith ( 'chrome-extension://' ) )
return fetch ( event . request ) ;
2021-10-12 05:52:28 +02:00
const request = event . request ;
2021-10-23 01:59:17 +02:00
const client = await self . clients . get ( event . clientId ) ;
2021-10-12 05:52:28 +02:00
2023-03-02 00:32:39 +01:00
// When trace viewer is deployed over https, we will force upgrade
// insecure http subresources to https. Otherwise, these will fail
// to load inside our https snapshots.
// In this case, we also match http resources from the archive by
// the https urls.
const isDeployedAsHttps = self . registration . scope . startsWith ( 'https://' ) ;
2021-10-13 20:07:29 +02:00
if ( request . url . startsWith ( self . registration . scope ) ) {
2023-01-31 04:07:52 +01:00
const url = new URL ( unwrapPopoutUrl ( request . url ) ) ;
2021-10-13 20:07:29 +02:00
const relativePath = url . pathname . substring ( scopePath . length - 1 ) ;
2021-10-23 00:14:58 +02:00
if ( relativePath === '/ping' ) {
2021-10-13 22:31:54 +02:00
await gc ( ) ;
2021-10-23 00:14:58 +02:00
return new Response ( null , { status : 200 } ) ;
}
2023-08-20 23:47:18 +02:00
const traceUrl = url . searchParams . get ( 'trace' ) ;
2021-11-10 00:12:37 +01:00
2023-02-28 07:31:47 +01:00
if ( relativePath === '/contexts' ) {
2021-11-11 21:31:19 +01:00
try {
2024-10-23 19:25:04 +02:00
const limit = url . searchParams . has ( 'limit' ) ? + url . searchParams . get ( 'limit' ) ! : undefined ;
2024-11-14 15:27:33 +01:00
const traceModel = await loadTrace ( traceUrl ! , url . searchParams . get ( 'traceFileName' ) , client , limit , ( done : number , total : number ) = > {
2021-11-11 21:31:19 +01:00
client . postMessage ( { method : 'progress' , params : { done , total } } ) ;
} ) ;
2023-02-28 07:31:47 +01: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 23:42:50 +02:00
}
2021-10-13 22:31:54 +02:00
2021-11-03 01:35:23 +01:00
if ( relativePath . startsWith ( '/snapshotInfo/' ) ) {
2023-08-20 23:47:18 +02:00
const { snapshotServer } = loadedTraces . get ( traceUrl ! ) || { } ;
2021-10-13 22:31:54 +02:00
if ( ! snapshotServer )
2021-10-12 23:42:50 +02:00
return new Response ( null , { status : 404 } ) ;
2021-11-03 01:35:23 +01:00
return snapshotServer . serveSnapshotInfo ( relativePath , url . searchParams ) ;
2021-10-12 23:42:50 +02:00
}
2021-10-13 22:31:54 +02:00
if ( relativePath . startsWith ( '/snapshot/' ) ) {
2023-08-20 23:47:18 +02:00
const { snapshotServer } = loadedTraces . get ( traceUrl ! ) || { } ;
2021-10-13 22:31:54 +02:00
if ( ! snapshotServer )
return new Response ( null , { status : 404 } ) ;
2023-03-02 00:32:39 +01:00
const response = snapshotServer . serveSnapshot ( relativePath , url . searchParams , url . href ) ;
if ( isDeployedAsHttps )
response . headers . set ( 'Content-Security-Policy' , 'upgrade-insecure-requests' ) ;
return response ;
2021-10-13 22:31:54 +02:00
}
2024-10-22 14:12:25 +02:00
if ( relativePath . startsWith ( '/closest-screenshot/' ) ) {
const { snapshotServer } = loadedTraces . get ( traceUrl ! ) || { } ;
if ( ! snapshotServer )
return new Response ( null , { status : 404 } ) ;
return snapshotServer . serveClosestScreenshot ( relativePath , url . searchParams ) ;
}
2021-10-13 22:31:54 +02:00
if ( relativePath . startsWith ( '/sha1/' ) ) {
2023-05-20 00:18:18 +02:00
// Sha1 for sources is based on the file path, can't load it of a random model.
2023-08-20 23:47:18 +02:00
const sha1 = relativePath . slice ( '/sha1/' . length ) ;
for ( const trace of loadedTraces . values ( ) ) {
const blob = await trace . traceModel . resourceForSha1 ( sha1 ) ;
if ( blob )
2024-07-31 15:20:36 +02:00
return new Response ( blob , { status : 200 , headers : downloadHeaders ( url . searchParams ) } ) ;
2021-10-13 22:31:54 +02:00
}
return new Response ( null , { status : 404 } ) ;
}
2024-11-14 15:27:33 +01:00
if ( relativePath . startsWith ( '/file/' ) ) {
const path = url . searchParams . get ( 'path' ) ! ;
const traceViewerServer = clientIdToTraceUrls . get ( event . clientId ? ? '' ) ? . traceViewerServer ;
if ( ! traceViewerServer )
throw new Error ( 'client is not initialized' ) ;
const response = await traceViewerServer . readFile ( path ) ;
if ( ! response )
return new Response ( null , { status : 404 } ) ;
return response ;
}
// Fallback for static assets.
2021-10-12 23:42:50 +02:00
return fetch ( event . request ) ;
2021-10-12 05:52:28 +02:00
}
2023-01-31 04:07:52 +01:00
const snapshotUrl = unwrapPopoutUrl ( client ! . url ) ;
2021-11-10 00:12:37 +01:00
const traceUrl = new URL ( snapshotUrl ) . searchParams . get ( 'trace' ) ! ;
const { snapshotServer } = loadedTraces . get ( traceUrl ) || { } ;
2021-10-12 23:42:50 +02:00
if ( ! snapshotServer )
return new Response ( null , { status : 404 } ) ;
2023-03-01 02:08:46 +01:00
const lookupUrls = [ request . url ] ;
2023-03-02 00:32:39 +01:00
if ( isDeployedAsHttps && request . url . startsWith ( 'https://' ) )
2023-03-01 02:08:46 +01:00
lookupUrls . push ( request . url . replace ( /^https/ , 'http' ) ) ;
2023-07-11 05:04:48 +02:00
return snapshotServer . serveResource ( lookupUrls , request . method , snapshotUrl ) ;
2021-10-13 22:31:54 +02:00
}
2024-07-31 15:20:36 +02:00
function downloadHeaders ( searchParams : URLSearchParams ) : Headers | undefined {
const name = searchParams . get ( 'dn' ) ;
const contentType = searchParams . get ( 'dct' ) ;
if ( ! name )
2023-08-17 10:57:28 +02:00
return ;
const headers = new Headers ( ) ;
2024-07-31 15:20:36 +02:00
headers . set ( 'Content-Disposition' , ` attachment; filename="attachment"; filename*=UTF-8'' ${ encodeURIComponent ( name ) } ` ) ;
if ( contentType )
headers . set ( 'Content-Type' , contentType ) ;
2023-08-17 10:57:28 +02:00
return headers ;
}
2021-10-13 22:31:54 +02:00
async function gc() {
2022-04-01 21:20:05 +02:00
const clients = await self . clients . matchAll ( ) ;
2021-10-13 22:31:54 +02:00
const usedTraces = new Set < string > ( ) ;
2022-04-01 21:20:05 +02:00
2024-10-23 19:25:04 +02:00
for ( const [ clientId , data ] of clientIdToTraceUrls ) {
2022-04-01 21:20:05 +02:00
// @ts-ignore
2024-10-23 19:25:04 +02:00
if ( ! clients . find ( c = > c . id === clientId ) ) {
2023-08-25 21:10:28 +02:00
clientIdToTraceUrls . delete ( clientId ) ;
2024-10-23 19:25:04 +02:00
continue ;
}
if ( data . limit !== undefined ) {
const ordered = [ . . . data . traceUrls ] ;
// Leave the newest requested traces.
data . traceUrls = new Set ( ordered . slice ( ordered . length - data . limit ) ) ;
}
data . traceUrls . forEach ( url = > usedTraces . add ( url ) ) ;
2021-10-13 22:31:54 +02:00
}
for ( const traceUrl of loadedTraces . keys ( ) ) {
if ( ! usedTraces . has ( traceUrl ) )
loadedTraces . delete ( traceUrl ) ;
}
2021-10-12 05:52:28 +02:00
}
2021-10-12 23:42:50 +02:00
// @ts-ignore
self . addEventListener ( 'fetch' , function ( event : FetchEvent ) {
2021-10-12 05:52:28 +02:00
event . respondWith ( doFetch ( event ) ) ;
} ) ;