fix(trace): survive sw restart (#37442)
This commit is contained in:
		
							parent
							
								
									a008e126c0
								
							
						
					
					
						commit
						25f89ac4d2
					
				|  | @ -4802,6 +4802,12 @@ | |||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/idb-keyval": { | ||||
|       "version": "6.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", | ||||
|       "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", | ||||
|       "license": "Apache-2.0" | ||||
|     }, | ||||
|     "node_modules/ignore": { | ||||
|       "version": "5.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", | ||||
|  | @ -8377,6 +8383,7 @@ | |||
|     "packages/trace-viewer": { | ||||
|       "version": "0.0.0", | ||||
|       "dependencies": { | ||||
|         "idb-keyval": "^6.2.2", | ||||
|         "yaml": "^2.6.0" | ||||
|       } | ||||
|     }, | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
|   "version": "0.0.0", | ||||
|   "type": "module", | ||||
|   "dependencies": { | ||||
|     "idb-keyval": "^6.2.2", | ||||
|     "yaml": "^2.6.0" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ | |||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| import * as idbKeyval from 'idb-keyval'; | ||||
| 
 | ||||
| import { splitProgress } from './progress'; | ||||
| import { unwrapPopoutUrl } from './snapshotRenderer'; | ||||
| import { SnapshotServer } from './snapshotServer'; | ||||
|  | @ -33,11 +35,14 @@ self.addEventListener('activate', function(event: any) { | |||
| }); | ||||
| 
 | ||||
| const scopePath = new URL(self.registration.scope).pathname; | ||||
| 
 | ||||
| const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer }>(); | ||||
| 
 | ||||
| const clientIdToTraceUrls = new Map<string, { limit: number | undefined, traceUrls: Set<string>, traceViewerServer: TraceViewerServer }>(); | ||||
| 
 | ||||
| function simulateServiceWorkerRestart() { | ||||
|   loadedTraces.clear(); | ||||
|   clientIdToTraceUrls.clear(); | ||||
| } | ||||
| 
 | ||||
| async function loadTrace(traceUrl: string, traceFileName: string | null, client: any | undefined, limit: number | undefined, progress: (done: number, total: number) => undefined): Promise<TraceModel> { | ||||
|   await gc(); | ||||
|   const clientId = client?.id ?? ''; | ||||
|  | @ -49,6 +54,7 @@ async function loadTrace(traceUrl: string, traceFileName: string | null, client: | |||
|     clientIdToTraceUrls.set(clientId, data); | ||||
|   } | ||||
|   data.traceUrls.add(traceUrl); | ||||
|   await saveClientIdParams(); | ||||
| 
 | ||||
|   const traceModel = new TraceModel(); | ||||
|   try { | ||||
|  | @ -101,6 +107,10 @@ async function doFetch(event: FetchEvent): Promise<Response> { | |||
|       await gc(); | ||||
|       return new Response(null, { status: 200 }); | ||||
|     } | ||||
|     if (relativePath === '/restartServiceWorker') { | ||||
|       simulateServiceWorkerRestart(); | ||||
|       return new Response(null, { status: 200 }); | ||||
|     } | ||||
| 
 | ||||
|     const traceUrl = url.searchParams.get('trace'); | ||||
| 
 | ||||
|  | @ -122,6 +132,16 @@ async function doFetch(event: FetchEvent): Promise<Response> { | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (!clientIdToTraceUrls.has(event.clientId)) { | ||||
|       // Service worker was restarted upon subresource fetch.
 | ||||
|       // It was stopped because ping did not keep it alive since the tab itself was throttled.
 | ||||
|       const params = await loadClientIdParams(event.clientId); | ||||
|       if (params) { | ||||
|         for (const traceUrl of params.traceUrls) | ||||
|           await loadTrace(traceUrl, null, client, params.limit, () => {}); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (relativePath.startsWith('/snapshotInfo/')) { | ||||
|       const { snapshotServer } = loadedTraces.get(traceUrl!) || {}; | ||||
|       if (!snapshotServer) | ||||
|  | @ -221,6 +241,36 @@ async function gc() { | |||
|     if (!usedTraces.has(traceUrl)) | ||||
|       loadedTraces.delete(traceUrl); | ||||
|   } | ||||
| 
 | ||||
|   await saveClientIdParams(); | ||||
| } | ||||
| 
 | ||||
| // Persist clientIdToTraceUrls to localStorage to avoid losing it when the service worker is restarted.
 | ||||
| async function saveClientIdParams() { | ||||
|   const serialized: Record<string, { | ||||
|     limit: number | undefined, | ||||
|     traceUrls: string[] | ||||
|   }> = {}; | ||||
|   for (const [clientId, data] of clientIdToTraceUrls) { | ||||
|     serialized[clientId] = { | ||||
|       limit: data.limit, | ||||
|       traceUrls: [...data.traceUrls] | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   const newValue = JSON.stringify(serialized); | ||||
|   const oldValue = await idbKeyval.get('clientIdToTraceUrls'); | ||||
|   if (newValue === oldValue) | ||||
|     return; | ||||
|   idbKeyval.set('clientIdToTraceUrls', newValue); | ||||
| } | ||||
| 
 | ||||
| async function loadClientIdParams(clientId: string): Promise<{ limit: number | undefined, traceUrls: string[] } | undefined> { | ||||
|   const serialized = await idbKeyval.get('clientIdToTraceUrls') as string | undefined; | ||||
|   if (!serialized) | ||||
|     return; | ||||
|   const deserialized = JSON.parse(serialized); | ||||
|   return deserialized[clientId]; | ||||
| } | ||||
| 
 | ||||
| // @ts-ignore
 | ||||
|  |  | |||
|  | @ -2021,3 +2021,22 @@ test.describe(() => { | |||
|     await expect(frame.getByRole('button')).toHaveCSS('color', 'rgb(255, 0, 0)'); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| test('should survive service worker restart', async ({ page, runAndTrace, server }) => { | ||||
|   const traceViewer = await runAndTrace(async () => { | ||||
|     await page.goto(server.EMPTY_PAGE); | ||||
|     await page.setContent('Old world'); | ||||
|     await page.evaluate(() => document.body.textContent = 'New world'); | ||||
|   }); | ||||
|   const snapshot1 = await traceViewer.snapshotFrame('Evaluate'); | ||||
|   await expect(snapshot1.locator('body')).toHaveText('New world'); | ||||
| 
 | ||||
|   const status = await traceViewer.page.evaluate(async () => { | ||||
|     const response = await fetch('restartServiceWorker'); | ||||
|     return response.status; | ||||
|   }); | ||||
|   expect(status).toBe(200); | ||||
| 
 | ||||
|   const snapshot2 = await traceViewer.snapshotFrame('Set content'); | ||||
|   await expect(snapshot2.locator('body')).toHaveText('Old world'); | ||||
| }); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue