| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Copyright Microsoft Corporation. All rights reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 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. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-11 22:52:17 +08:00
										 |  |  | import type { Frame, Page } from 'playwright-core'; | 
					
						
							| 
									
										
										
										
											2025-02-14 08:15:11 +08:00
										 |  |  | import { ZipFile } from '../../packages/playwright-core/lib/server/utils/zipFile'; | 
					
						
							| 
									
										
										
										
											2024-09-27 02:22:20 +08:00
										 |  |  | import type { TraceModelBackend } from '../../packages/trace-viewer/src/sw/traceModel'; | 
					
						
							| 
									
										
										
										
											2023-02-28 14:31:47 +08:00
										 |  |  | import type { StackFrame } from '../../packages/protocol/src/channels'; | 
					
						
							| 
									
										
										
										
											2023-03-15 06:58:55 +08:00
										 |  |  | import { parseClientSideCallMetadata } from '../../packages/playwright-core/lib/utils/isomorphic/traceUtils'; | 
					
						
							| 
									
										
										
										
											2024-09-27 02:22:20 +08:00
										 |  |  | import { TraceModel } from '../../packages/trace-viewer/src/sw/traceModel'; | 
					
						
							| 
									
										
										
										
											2023-07-06 02:20:28 +08:00
										 |  |  | import type { ActionTreeItem } from '../../packages/trace-viewer/src/ui/modelUtil'; | 
					
						
							|  |  |  | import { buildActionTree, MultiTraceModel } from '../../packages/trace-viewer/src/ui/modelUtil'; | 
					
						
							| 
									
										
										
										
											2023-09-20 07:21:09 +08:00
										 |  |  | import type { ActionTraceEvent, ConsoleMessageTraceEvent, EventTraceEvent, TraceEvent } from '@trace/trace'; | 
					
						
							| 
									
										
										
										
											2024-09-10 04:12:20 +08:00
										 |  |  | import style from 'ansi-styles'; | 
					
						
							| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export async function attachFrame(page: Page, frameId: string, url: string): Promise<Frame> { | 
					
						
							|  |  |  |   const handle = await page.evaluateHandle(async ({ frameId, url }) => { | 
					
						
							|  |  |  |     const frame = document.createElement('iframe'); | 
					
						
							|  |  |  |     frame.src = url; | 
					
						
							|  |  |  |     frame.id = frameId; | 
					
						
							|  |  |  |     document.body.appendChild(frame); | 
					
						
							|  |  |  |     await new Promise(x => frame.onload = x); | 
					
						
							|  |  |  |     return frame; | 
					
						
							|  |  |  |   }, { frameId, url }); | 
					
						
							| 
									
										
										
										
											2023-02-28 07:29:20 +08:00
										 |  |  |   return handle.asElement().contentFrame() as Promise<Frame>; | 
					
						
							| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export async function detachFrame(page: Page, frameId: string) { | 
					
						
							|  |  |  |   await page.evaluate(frameId => { | 
					
						
							| 
									
										
										
										
											2023-02-28 07:29:20 +08:00
										 |  |  |     document.getElementById(frameId)!.remove(); | 
					
						
							| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  |   }, frameId); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export async function verifyViewport(page: Page, width: number, height: number) { | 
					
						
							| 
									
										
										
										
											2023-01-04 09:30:36 +08:00
										 |  |  |   // `expect` may clash in test runner tests if imported eagerly.
 | 
					
						
							|  |  |  |   const { expect } = require('@playwright/test'); | 
					
						
							| 
									
										
										
										
											2023-02-28 07:29:20 +08:00
										 |  |  |   expect(page.viewportSize()!.width).toBe(width); | 
					
						
							|  |  |  |   expect(page.viewportSize()!.height).toBe(height); | 
					
						
							| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  |   expect(await page.evaluate('window.innerWidth')).toBe(width); | 
					
						
							|  |  |  |   expect(await page.evaluate('window.innerHeight')).toBe(height); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-23 23:59:07 +08:00
										 |  |  | export function expectedSSLError(browserName: string, platform: string): RegExp { | 
					
						
							|  |  |  |   if (browserName === 'chromium') | 
					
						
							|  |  |  |     return /net::(ERR_CERT_AUTHORITY_INVALID|ERR_CERT_INVALID)/; | 
					
						
							|  |  |  |   if (browserName === 'webkit') { | 
					
						
							| 
									
										
										
										
											2023-08-01 02:24:04 +08:00
										 |  |  |     if (platform === 'darwin') | 
					
						
							| 
									
										
										
										
											2023-08-23 23:59:07 +08:00
										 |  |  |       return /The certificate for this server is invalid/; | 
					
						
							| 
									
										
										
										
											2023-08-01 02:24:04 +08:00
										 |  |  |     else if (platform === 'win32') | 
					
						
							| 
									
										
										
										
											2023-08-23 23:59:07 +08:00
										 |  |  |       return /SSL peer certificate or SSH remote key was not OK/; | 
					
						
							| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  |     else | 
					
						
							| 
									
										
										
										
											2024-10-07 17:58:03 +08:00
										 |  |  |       return /Unacceptable TLS certificate|Operation was cancelled/; | 
					
						
							| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-08-23 23:59:07 +08:00
										 |  |  |   return /SSL_ERROR_UNKNOWN/; | 
					
						
							| 
									
										
										
										
											2020-09-26 14:30:46 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-05 02:59:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function chromiumVersionLessThan(a: string, b: string) { | 
					
						
							|  |  |  |   const left: number[] = a.split('.').map(e => Number(e)); | 
					
						
							|  |  |  |   const right: number[] = b.split('.').map(e => Number(e)); | 
					
						
							|  |  |  |   for (let i = 0; i < 4; i++) { | 
					
						
							|  |  |  |     if (left[i] > right[i]) | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     if (left[i] < right[i]) | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							| 
									
										
										
										
											2021-09-09 11:32:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let didSuppressUnverifiedCertificateWarning = false; | 
					
						
							|  |  |  | let originalEmitWarning: (warning: string | Error, ...args: any[]) => void; | 
					
						
							|  |  |  | export function suppressCertificateWarning() { | 
					
						
							|  |  |  |   if (didSuppressUnverifiedCertificateWarning) | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   didSuppressUnverifiedCertificateWarning = true; | 
					
						
							| 
									
										
										
										
											2023-10-05 10:56:42 +08:00
										 |  |  |   // Suppress one-time warning:
 | 
					
						
							| 
									
										
										
										
											2021-09-09 11:32:52 +08:00
										 |  |  |   // https://github.com/nodejs/node/blob/1bbe66f432591aea83555d27dd76c55fea040a0d/lib/internal/options.js#L37-L49
 | 
					
						
							|  |  |  |   originalEmitWarning = process.emitWarning; | 
					
						
							|  |  |  |   process.emitWarning = (warning, ...args) => { | 
					
						
							|  |  |  |     if (typeof warning === 'string' && warning.includes('NODE_TLS_REJECT_UNAUTHORIZED')) { | 
					
						
							|  |  |  |       process.emitWarning = originalEmitWarning; | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return originalEmitWarning.call(process, warning, ...args); | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2021-12-10 09:21:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-08 08:16:26 +08:00
										 |  |  | export async function parseTraceRaw(file: string): Promise<{ events: any[], resources: Map<string, Buffer>, actions: string[], actionObjects: ActionTraceEvent[], stacks: Map<string, StackFrame[]> }> { | 
					
						
							| 
									
										
										
										
											2022-06-18 07:11:22 +08:00
										 |  |  |   const zipFS = new ZipFile(file); | 
					
						
							| 
									
										
										
										
											2021-12-10 09:21:17 +08:00
										 |  |  |   const resources = new Map<string, Buffer>(); | 
					
						
							|  |  |  |   for (const entry of await zipFS.entries()) | 
					
						
							|  |  |  |     resources.set(entry, await zipFS.read(entry)); | 
					
						
							|  |  |  |   zipFS.close(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-16 13:33:40 +08:00
										 |  |  |   const actionMap = new Map<string, ActionTraceEvent>(); | 
					
						
							| 
									
										
										
										
											2023-02-28 07:29:20 +08:00
										 |  |  |   const events: any[] = []; | 
					
						
							| 
									
										
										
										
											2023-02-28 14:31:47 +08:00
										 |  |  |   for (const traceFile of [...resources.keys()].filter(name => name.endsWith('.trace'))) { | 
					
						
							|  |  |  |     for (const line of resources.get(traceFile)!.toString().split('\n')) { | 
					
						
							| 
									
										
										
										
											2023-03-16 13:33:40 +08:00
										 |  |  |       if (line) { | 
					
						
							|  |  |  |         const event = JSON.parse(line) as TraceEvent; | 
					
						
							| 
									
										
										
										
											2023-07-08 08:16:26 +08:00
										 |  |  |         events.push(event); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-16 13:33:40 +08:00
										 |  |  |         if (event.type === 'before') { | 
					
						
							|  |  |  |           const action: ActionTraceEvent = { | 
					
						
							|  |  |  |             ...event, | 
					
						
							|  |  |  |             type: 'action', | 
					
						
							|  |  |  |             endTime: 0, | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           actionMap.set(event.callId, action); | 
					
						
							|  |  |  |         } else if (event.type === 'input') { | 
					
						
							|  |  |  |           const existing = actionMap.get(event.callId); | 
					
						
							|  |  |  |           existing.inputSnapshot = event.inputSnapshot; | 
					
						
							|  |  |  |           existing.point = event.point; | 
					
						
							|  |  |  |         } else if (event.type === 'after') { | 
					
						
							|  |  |  |           const existing = actionMap.get(event.callId); | 
					
						
							|  |  |  |           existing.afterSnapshot = event.afterSnapshot; | 
					
						
							|  |  |  |           existing.endTime = event.endTime; | 
					
						
							|  |  |  |           existing.error = event.error; | 
					
						
							|  |  |  |           existing.result = event.result; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-02-28 14:31:47 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const networkFile of [...resources.keys()].filter(name => name.endsWith('.network'))) { | 
					
						
							|  |  |  |     for (const line of resources.get(networkFile)!.toString().split('\n')) { | 
					
						
							|  |  |  |       if (line) | 
					
						
							|  |  |  |         events.push(JSON.parse(line)); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-12-10 09:21:17 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-02-23 13:08:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-28 14:31:47 +08:00
										 |  |  |   const stacks: Map<string, StackFrame[]> = new Map(); | 
					
						
							|  |  |  |   for (const stacksFile of [...resources.keys()].filter(name => name.endsWith('.stacks'))) { | 
					
						
							|  |  |  |     for (const [key, value] of parseClientSideCallMetadata(JSON.parse(resources.get(stacksFile)!.toString()))) | 
					
						
							|  |  |  |       stacks.set(key, value); | 
					
						
							| 
									
										
										
										
											2021-12-10 09:21:17 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-02-23 13:08:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-08 08:16:26 +08:00
										 |  |  |   const actionObjects = [...actionMap.values()]; | 
					
						
							|  |  |  |   actionObjects.sort((a, b) => a.startTime - b.startTime); | 
					
						
							| 
									
										
										
										
											2021-12-10 09:21:17 +08:00
										 |  |  |   return { | 
					
						
							|  |  |  |     events, | 
					
						
							|  |  |  |     resources, | 
					
						
							| 
									
										
										
										
											2025-05-20 07:01:45 +08:00
										 |  |  |     actions: actionObjects.map(a => a.title ?? a.class.toLowerCase() + '.' + a.method), | 
					
						
							| 
									
										
										
										
											2023-07-08 08:16:26 +08:00
										 |  |  |     actionObjects, | 
					
						
							| 
									
										
										
										
											2023-02-23 13:08:47 +08:00
										 |  |  |     stacks, | 
					
						
							| 
									
										
										
										
											2021-12-10 09:21:17 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-06-17 07:33:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-20 07:01:45 +08:00
										 |  |  | export async function parseTrace(file: string): Promise<{ resources: Map<string, Buffer>, events: (EventTraceEvent | ConsoleMessageTraceEvent)[], actions: ActionTraceEvent[], titles: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[], errors: string[] }> { | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  |   const backend = new TraceBackend(file); | 
					
						
							|  |  |  |   const traceModel = new TraceModel(); | 
					
						
							| 
									
										
										
										
											2024-09-27 07:46:27 +08:00
										 |  |  |   await traceModel.load(backend, () => {}); | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  |   const model = new MultiTraceModel(traceModel.contextEntries); | 
					
						
							| 
									
										
										
										
											2023-07-06 02:20:28 +08:00
										 |  |  |   const { rootItem } = buildActionTree(model.actions); | 
					
						
							|  |  |  |   const actionTree: string[] = []; | 
					
						
							|  |  |  |   const visit = (actionItem: ActionTreeItem, indent: string) => { | 
					
						
							| 
									
										
										
										
											2025-05-20 07:01:45 +08:00
										 |  |  |     actionTree.push(`${indent}${actionItem.action?.title || actionItem.id}`); | 
					
						
							| 
									
										
										
										
											2023-07-06 02:20:28 +08:00
										 |  |  |     for (const child of actionItem.children) | 
					
						
							|  |  |  |       visit(child, indent + '  '); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   rootItem.children.forEach(a => visit(a, '')); | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  |   return { | 
					
						
							| 
									
										
										
										
											2025-05-20 07:01:45 +08:00
										 |  |  |     titles: model.actions.map(a => a.title ?? a.class.toLowerCase() + '.' + a.method), | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  |     resources: backend.entries, | 
					
						
							|  |  |  |     actions: model.actions, | 
					
						
							|  |  |  |     events: model.events, | 
					
						
							| 
									
										
										
										
											2023-11-17 03:37:57 +08:00
										 |  |  |     errors: model.errors.map(e => e.message), | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  |     model, | 
					
						
							|  |  |  |     traceModel, | 
					
						
							| 
									
										
										
										
											2023-07-06 02:20:28 +08:00
										 |  |  |     actionTree, | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-17 07:33:32 +08:00
										 |  |  | export async function parseHar(file: string): Promise<Map<string, Buffer>> { | 
					
						
							| 
									
										
										
										
											2022-06-18 07:11:22 +08:00
										 |  |  |   const zipFS = new ZipFile(file); | 
					
						
							| 
									
										
										
										
											2022-06-17 07:33:32 +08:00
										 |  |  |   const resources = new Map<string, Buffer>(); | 
					
						
							|  |  |  |   for (const entry of await zipFS.entries()) | 
					
						
							|  |  |  |     resources.set(entry, await zipFS.read(entry)); | 
					
						
							|  |  |  |   zipFS.close(); | 
					
						
							|  |  |  |   return resources; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-07-02 04:57:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function waitForTestLog<T>(page: Page, prefix: string): Promise<T> { | 
					
						
							|  |  |  |   return new Promise<T>(resolve => { | 
					
						
							|  |  |  |     page.on('console', message => { | 
					
						
							|  |  |  |       const text = message.text(); | 
					
						
							|  |  |  |       if (text.startsWith(prefix)) { | 
					
						
							|  |  |  |         const json = text.substring(prefix.length); | 
					
						
							|  |  |  |         resolve(JSON.parse(json)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-11 06:10:25 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const ansiRegex = new RegExp('[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', 'g'); | 
					
						
							|  |  |  | export function stripAnsi(str: string): string { | 
					
						
							|  |  |  |   return str.replace(ansiRegex, ''); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 04:12:20 +08:00
										 |  |  | export function ansi2Markup(text: string): string { | 
					
						
							|  |  |  |   return text.replace(ansiRegex, match => { | 
					
						
							|  |  |  |     switch (match) { | 
					
						
							|  |  |  |       case style.inverse.open: | 
					
						
							|  |  |  |         return '<i>'; | 
					
						
							|  |  |  |       case style.inverse.close: | 
					
						
							|  |  |  |         return '</i>'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case style.bold.open: | 
					
						
							|  |  |  |         return '<b>'; | 
					
						
							|  |  |  |       case style.dim.open: | 
					
						
							|  |  |  |         return '<d>'; | 
					
						
							|  |  |  |       case style.green.open: | 
					
						
							|  |  |  |         return '<g>'; | 
					
						
							|  |  |  |       case style.red.open: | 
					
						
							|  |  |  |         return '<r>'; | 
					
						
							|  |  |  |       case style.yellow.open: | 
					
						
							|  |  |  |         return '<y>'; | 
					
						
							|  |  |  |       case style.bgYellow.open: | 
					
						
							|  |  |  |         return '<Y>'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case style.bold.close: | 
					
						
							|  |  |  |       case style.dim.close: | 
					
						
							|  |  |  |       case style.green.close: | 
					
						
							|  |  |  |       case style.red.close: | 
					
						
							|  |  |  |       case style.yellow.close: | 
					
						
							|  |  |  |       case style.bgYellow.close: | 
					
						
							|  |  |  |         return '</>'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         return match; // unexpected escape sequence
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-05-06 06:12:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class TraceBackend implements TraceModelBackend { | 
					
						
							|  |  |  |   private _fileName: string; | 
					
						
							|  |  |  |   private _entriesPromise: Promise<Map<string, Buffer>>; | 
					
						
							|  |  |  |   readonly entries = new Map<string, Buffer>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(fileName: string) { | 
					
						
							|  |  |  |     this._fileName = fileName; | 
					
						
							|  |  |  |     this._entriesPromise = this._readEntries(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async _readEntries(): Promise<Map<string, Buffer>> { | 
					
						
							|  |  |  |     const zipFS = new ZipFile(this._fileName); | 
					
						
							|  |  |  |     for (const entry of await zipFS.entries()) | 
					
						
							|  |  |  |       this.entries.set(entry, await zipFS.read(entry)); | 
					
						
							|  |  |  |     zipFS.close(); | 
					
						
							|  |  |  |     return this.entries; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   isLive() { | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   traceURL() { | 
					
						
							|  |  |  |     return 'file://' + this._fileName; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async entryNames(): Promise<string[]> { | 
					
						
							|  |  |  |     const entries = await this._entriesPromise; | 
					
						
							|  |  |  |     return [...entries.keys()]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async hasEntry(entryName: string): Promise<boolean> { | 
					
						
							|  |  |  |     const entries = await this._entriesPromise; | 
					
						
							|  |  |  |     return entries.has(entryName); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async readText(entryName: string): Promise<string | undefined> { | 
					
						
							|  |  |  |     const entries = await this._entriesPromise; | 
					
						
							|  |  |  |     const entry = entries.get(entryName); | 
					
						
							|  |  |  |     if (!entry) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     return entry.toString(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async readBlob(entryName: string) { | 
					
						
							|  |  |  |     const entries = await this._entriesPromise; | 
					
						
							|  |  |  |     const entry = entries.get(entryName); | 
					
						
							|  |  |  |     return entry as any; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |