diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index 63e0900e598fc..401ff521c67d2 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -154,8 +154,19 @@ jobs: strategy: fail-fast: false matrix: - channel: [chrome, chrome-beta, msedge, msedge-beta, msedge-dev] - runs-on: [ubuntu-22.04, macos-latest, windows-latest] + include: + - channel: chrome + runs-on: ubuntu-22.04 + - channel: chrome + runs-on: macos-latest + - channel: chrome + runs-on: windows-latest + - channel: chrome-beta + runs-on: ubuntu-22.04 + - channel: msedge + runs-on: windows-latest + - channel: msedge-dev + runs-on: windows-latest steps: - uses: actions/checkout@v6 - uses: ./.github/actions/run-test diff --git a/.github/workflows/tests_video.yml b/.github/workflows/tests_video.yml deleted file mode 100644 index 170c7cf17de56..0000000000000 --- a/.github/workflows/tests_video.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: "tests Video" - -on: - push: - branches: - - main - - release-* - -env: - # Force terminal colors. @see https://www.npmjs.com/package/colors - FORCE_COLOR: 1 - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - -jobs: - video_linux: - name: "Video Linux" - environment: allow-uploading-flakiness-results - strategy: - fail-fast: false - matrix: - browser: [chromium, firefox, webkit] - os: [ubuntu-22.04, ubuntu-24.04] - permissions: - id-token: write # This is required for OIDC login (azure/login) to succeed - contents: read # This is required for actions/checkout to succeed - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - - uses: ./.github/actions/run-test - with: - browsers-to-install: ${{ matrix.browser }} chromium - command: npm run test -- --project=${{ matrix.browser }}-* - bot-name: "${{ matrix.browser }}-${{ matrix.os }}" - flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} - flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} - flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} - env: - PWTEST_VIDEO: 1 diff --git a/docs/src/test-cli-js.md b/docs/src/test-cli-js.md index c6c744b242f66..d5803a9b1aad8 100644 --- a/docs/src/test-cli-js.md +++ b/docs/src/test-cli-js.md @@ -86,7 +86,7 @@ npx playwright test --ui | `--fully-parallel` | Run all tests in parallel (default: false). | | `--global-timeout ` | Maximum time this test suite can run in milliseconds (default: unlimited). | | `-g ` or `--grep ` | Only run tests matching this regular expression (default: ".*"). | -| `--grep-invert ` | Only run tests that do not match this regular expression. | +| `-G ` or `--grep-invert ` | Only run tests that do not match this regular expression. | | `--headed` | Run tests in headed browsers (default: headless). | | `--ignore-snapshots` | Ignore screenshot and snapshot expectations. | | `-j ` or `--workers ` | Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%). | diff --git a/package-lock.json b/package-lock.json index d8c81d976a17a..6c91e9bd16d2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "@types/source-map-support": "^0.5.4", "@types/ws": "8.18.1", "@types/xml2js": "^0.4.9", + "@types/yauzl": "^2.10.3", "@types/yazl": "^2.4.2", "@typescript-eslint/eslint-plugin": "^8.59.0", "@typescript-eslint/parser": "^8.59.0", @@ -120,6 +121,7 @@ "ws": "8.17.1", "xml2js": "^0.5.0", "yaml": "^2.8.3", + "yauzl": "3.3.2", "yazl": "2.5.1", "zod": "^4.3.6", "zod-to-json-schema": "^3.25.1" @@ -2819,7 +2821,6 @@ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, - "optional": true, "dependencies": { "@types/node": "*" } @@ -9506,6 +9507,19 @@ "node": ">=10" } }, + "node_modules/yauzl": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.2.tgz", + "integrity": "sha512-Md9ankxxN23wncAN8s7+Tn3Co52zLUPMtnrLAbVCnfG5d2tKBFfmygYSgXlqFgXObtzIgqkx7aNgDBpso9+4qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yazl": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", diff --git a/package.json b/package.json index 91fa4051e624e..631a2dfae5134 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "@types/source-map-support": "^0.5.4", "@types/ws": "8.18.1", "@types/xml2js": "^0.4.9", + "@types/yauzl": "^2.10.3", "@types/yazl": "^2.4.2", "@typescript-eslint/eslint-plugin": "^8.59.0", "@typescript-eslint/parser": "^8.59.0", @@ -162,6 +163,7 @@ "ws": "8.17.1", "xml2js": "^0.5.0", "yaml": "^2.8.3", + "yauzl": "3.3.2", "yazl": "2.5.1", "zod": "^4.3.6", "zod-to-json-schema": "^3.25.1" diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 67ab17bdc7b78..a496fd6b172d6 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -45,7 +45,7 @@ }, { "name": "webkit", - "revision": "2299", + "revision": "2300", "installByDefault": true, "revisionOverrides": { "mac14": "2251", diff --git a/packages/playwright-core/src/cli/installActions.ts b/packages/playwright-core/src/cli/installActions.ts index ee95b3e4f1428..18258da128dc4 100644 --- a/packages/playwright-core/src/cli/installActions.ts +++ b/packages/playwright-core/src/cli/installActions.ts @@ -87,7 +87,9 @@ export async function markDockerImage(dockerImageNameTemplate: string) { await writeDockerVersion(dockerImageNameTemplate); } -export async function installBrowsers(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean }) { +export async function installBrowsers(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean, progress?: boolean }) { + if (options.progress === false) + process.env.PLAYWRIGHT_DOWNLOAD_NO_PROGRESS = '1'; if (isLikelyNpxGlobal()) { console.error(wrapInASCIIBox([ `WARNING: It looks like you are running 'npx playwright install' without first`, diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index afb51d56bea57..7eac07ab0bd3c 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -80,7 +80,8 @@ export function decorateProgram(program: Command) { .option('--force', 'force reinstall of already installed browsers') .option('--only-shell', 'only install headless shell when installing chromium') .option('--no-shell', 'do not install chromium headless shell') - .action(async function(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean }) { + .option('--no-progress', 'do not show download progress bars') + .action(async function(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean, progress?: boolean }) { try { await installBrowsers(args, options); } catch (e) { diff --git a/packages/playwright-core/src/server/DEPS.list b/packages/playwright-core/src/server/DEPS.list index b6267405e2d6f..f9cf05a0b315c 100644 --- a/packages/playwright-core/src/server/DEPS.list +++ b/packages/playwright-core/src/server/DEPS.list @@ -16,6 +16,7 @@ node_modules/pngjs node_modules/proxy-from-env node_modules/ws node_modules/yaml +node_modules/yauzl node_modules/yazl [devtoolsController.ts] diff --git a/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts b/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts index cad9e8982dd41..c413733c4b067 100644 --- a/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts +++ b/packages/playwright-core/src/server/bidi/bidiNetworkManager.ts @@ -287,7 +287,7 @@ class BidiRequest { const postDataBuffer = null; this.request = new network.Request(frame._page.browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigation ?? undefined, payload.request.url, resourceTypeFromBidi(payload.request.destination, payload.request.initiatorType, payload.initiator?.type), payload.request.method, - postDataBuffer, headersOverride || fromBidiHeaders(payload.request.headers)); + postDataBuffer, headersOverride || fromBidiHeaders(payload.request.headers), payload.timestamp); // "raw" headers are the same as "provisional" headers in Bidi. this.request.setRawRequestHeaders(null); this.request._setBodySize(payload.request.bodySize || 0); diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index 82da90b57b492..3bc23056da0dd 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -76,7 +76,7 @@ export class CRNetworkManager { if (this._page) { sessionInfo.eventListeners.push(...[ eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page!.frameManager.onWebSocketCreated(e.requestId, e.url)), - eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers, '\n'), e.wallTime, e.timestamp)), + eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page!.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers, '\n'), e.wallTime * 1000, e.timestamp)), eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page!.frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText, headersObjectToArray(e.response.headers, '\n'))), eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page!.frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)), eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page!.frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)), @@ -605,7 +605,7 @@ class InterceptableRequest { if (entries && entries.length) postDataBuffer = Buffer.concat(entries.map(entry => Buffer.from(entry.bytes!, 'base64'))); - this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, toResourceType(requestWillBeSentEvent.type || 'Other'), method, postDataBuffer, headersOverride || headersObjectToArray(headers)); + this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, toResourceType(requestWillBeSentEvent.type || 'Other'), method, postDataBuffer, headersOverride || headersObjectToArray(headers), requestWillBeSentEvent.wallTime * 1000); } } diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 80e9e1b27af6f..c269a97763069 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -178,11 +178,11 @@ export class FFPage implements PageDelegate { } _onWebSocketFrameReceived(event: Protocol.Page.webSocketFrameReceivedPayload) { - this._page.frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp); + this._page.frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp * 1000); } _onWebSocketFrameSent(event: Protocol.Page.webSocketFrameSentPayload) { - this._page.frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp); + this._page.frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data, event.timestamp * 1000); } _onExecutionContextCreated(payload: Protocol.Runtime.executionContextCreatedPayload) { diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 88894ea2bff74..d1eeedeb9398b 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -408,10 +408,12 @@ export class FrameManager { if (!ws) return; + ws.setRequestTiming(wallTime, timestamp); + if (ws.markAsNotified()) this._page.emit(Page.Events.WebSocket, ws); - ws.requestSent(headers, wallTime, timestamp); + ws.requestSent(headers); } onWebSocketResponse(requestId: string, status: number, statusText: string, headers: types.HeadersArray) { diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 6b38c72e464b5..458d98f90e01a 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -284,7 +284,7 @@ export class HarTracer { return; const pageEntry = this._createPageEntryIfNeeded(page); - const harEntry = createHarEntry(pageEntry?.id, request.method(), url, request.frame()?.guid, this._options); + const harEntry = createHarEntry(pageEntry?.id, request.method(), url, request.frame()?.guid, this._options, request.wallTimeMs()); harEntry._resourceType = request.resourceType(); this._recordRequestHeadersAndCookies(harEntry, request.headers()); harEntry.request.postData = this._postDataForRequest(request, this._options.content); @@ -441,7 +441,7 @@ export class HarTracer { return; const pageEntry = this._createPageEntryIfNeeded(page); - const harEntry = createHarEntry(pageEntry?.id, 'GET', url, page.mainFrame().guid, this._options); + const harEntry = createHarEntry(pageEntry?.id, 'GET', url, page.mainFrame().guid, this._options, webSocket.wallTimeMs()); harEntry._resourceType = 'websocket'; harEntry._webSocketMessages = []; @@ -535,6 +535,11 @@ export class HarTracer { const timing = response.timing(); if (pageEntry && startDateTime > timing.startTime) pageEntry.startedDateTime = new Date(timing.startTime).toISOString(); + if (request.wallTimeMs() === undefined && timing.startTime > 0) { + const startedDateTime = safeDateToISOString(timing.startTime); + if (startedDateTime) + harEntry.startedDateTime = startedDateTime; + } const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1; const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1; const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1; @@ -670,10 +675,11 @@ export class HarTracer { } -function createHarEntry(pageRef: string | undefined, method: string, url: URL, frameref: string | undefined, options: HarTracerOptions): har.Entry { +function createHarEntry(pageRef: string | undefined, method: string, url: URL, frameref: string | undefined, options: HarTracerOptions, wallTime?: number): har.Entry { + const startedDateTime = (wallTime && safeDateToISOString(wallTime)) || new Date().toISOString(); const harEntry: har.Entry = { pageref: pageRef, - startedDateTime: new Date().toISOString(), + startedDateTime, time: -1, request: { method: method, diff --git a/packages/playwright-core/src/server/localUtils.ts b/packages/playwright-core/src/server/localUtils.ts index 3880e2f5dde7e..8da82e56b2bbf 100644 --- a/packages/playwright-core/src/server/localUtils.ts +++ b/packages/playwright-core/src/server/localUtils.ts @@ -18,8 +18,8 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; +import * as yauzl from 'yauzl'; import * as yazl from 'yazl'; -import * as yauzl from '@utils/third_party/yauzl'; import { ManualPromise } from '@isomorphic/manualPromise'; import { serializeClientSideCallMetadata } from '@isomorphic/trace/traceUtils'; import { assert } from '@isomorphic/assert'; diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index 7e944987753d3..c156099cdaa5a 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -189,6 +189,7 @@ export class Request extends SdkObject { _responseEndTiming = -1; private _overrides: NormalizedContinueOverrides | undefined; private _bodySize: number | undefined; + private _wallTimeMs: number | undefined; _responseBodyOverride: { body: string; isBase64: boolean; } | undefined; static Events = { @@ -196,7 +197,7 @@ export class Request extends SdkObject { }; constructor(context: contexts.BrowserContext, frame: frames.Frame | null, serviceWorker: pages.Worker | null, redirectedFrom: Request | null, documentId: string | undefined, - url: string, resourceType: ResourceType, method: string, postData: Buffer | null, headers: HeadersArray) { + url: string, resourceType: ResourceType, method: string, postData: Buffer | null, headers: HeadersArray, wallTimeMs?: number) { super(frame || context, 'request'); assert(!url.startsWith('data:'), 'Data urls should not fire requests'); this._context = context; @@ -211,9 +212,14 @@ export class Request extends SdkObject { this._method = method; this._postData = postData; this._headers = headers; + this._wallTimeMs = wallTimeMs; this._isFavicon = url.endsWith('/favicon.ico') || !!redirectedFrom?._isFavicon; } + wallTimeMs(): number | undefined { + return this._wallTimeMs; + } + async raceWithPageClosure(progress: Progress, promise: Promise): Promise { const scope = this._serviceWorker?.openScope ?? this._frame?._page.openScope; if (scope) @@ -725,7 +731,7 @@ export class Response extends SdkObject { export class WebSocket extends SdkObject { private _url: string; private _notified = false; - private _requestWallTime: number | undefined; + private _requestWallTimeMs: number | undefined; private _requestTimestamp: number | undefined; private _status: number | undefined; private _statusText: string | undefined; @@ -760,10 +766,16 @@ export class WebSocket extends SdkObject { return this._url; } - requestSent(headers: HeadersArray, wallTime?: number, timestamp?: number) { - this._requestWallTime = wallTime; + wallTimeMs(): number | undefined { + return this._requestWallTimeMs; + } + + setRequestTiming(wallTimeMs: number | undefined, timestamp: number | undefined) { + this._requestWallTimeMs = wallTimeMs; this._requestTimestamp = timestamp; + } + requestSent(headers: HeadersArray) { this.emit(WebSocket.Events.Request, { headers }); } @@ -773,8 +785,8 @@ export class WebSocket extends SdkObject { private _toWallTime(timestamp: number): number { // The timestamp of each frame is relative to the timestamp (and walltime) of the initial request in Chromium and WebKit. - if (this._requestWallTime !== undefined && this._requestTimestamp !== undefined) - return this._requestWallTime + (timestamp - this._requestTimestamp); + if (this._requestWallTimeMs !== undefined && this._requestTimestamp !== undefined) + return this._requestWallTimeMs + (timestamp - this._requestTimestamp); // The timestamp is already a walltime in Firefox. return timestamp; diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index c0c25d3fd5f4f..06acef879795d 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -18,6 +18,7 @@ import fs from 'fs'; import path from 'path'; import { ManualPromise } from '@isomorphic/manualPromise'; +import { getAsBooleanFromENV } from '@utils/env'; import { httpRequest } from '@utils/network'; import { removeFolders } from '@utils/fileUtils'; import { extractZip } from '@utils/third_party/extractZip'; @@ -48,6 +49,7 @@ function downloadFile(options: DownloadParams): Promise { let downloadedBytes = 0; let totalBytes = 0; let chunked = false; + const reportProgress = !getAsBooleanFromENV('PLAYWRIGHT_DOWNLOAD_NO_PROGRESS'); const promise = new ManualPromise(); httpRequest({ @@ -106,7 +108,7 @@ function downloadFile(options: DownloadParams): Promise { function onData(chunk: string) { downloadedBytes += chunk.length; - if (!chunked) + if (!chunked && reportProgress) progress(downloadedBytes, totalBytes); } } diff --git a/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts b/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts index ec0fdb1a71663..b88806940b4eb 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvInterceptableRequest.ts @@ -62,7 +62,7 @@ export class WVInterceptableRequest { postDataBuffer = Buffer.from(event.request.postData, 'utf8'); } this.request = new network.Request(frame._page.browserContext, frame, null, redirectedFrom?.request || null, documentId, event.request.url, - resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers)); + resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers), this._wallTime); } adoptRequestFromNewProcess(newSession: WVSession, requestId: string) { diff --git a/packages/playwright-core/src/server/webkit/webview/wvPage.ts b/packages/playwright-core/src/server/webkit/webview/wvPage.ts index eb363811df291..5e41bf4a2cf7a 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvPage.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvPage.ts @@ -178,6 +178,7 @@ export class WVPage implements PageDelegate { session.send('Runtime.enable'), session.sendMayFail('Network.enable'), session.sendMayFail('Console.enable'), + this._workers.initializeSession(session), session.sendMayFail('Page.setBootstrapScript', { source: this._calculateBootstrapScript() }), session.sendMayFail('Runtime.evaluate', { expression: saveGlobalsSnapshotSource, returnByValue: true } as any), ]); @@ -326,7 +327,7 @@ export class WVPage implements PageDelegate { eventsHelper.addEventListener(session, 'Network.loadingFinished', e => this._onLoadingFinished(e)), eventsHelper.addEventListener(session, 'Network.loadingFailed', e => this._onLoadingFailed(session, e)), eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page.frameManager.onWebSocketCreated(e.requestId, e.url)), - eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers), e.walltime, e.timestamp)), + eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers), e.walltime * 1000, e.timestamp)), eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page.frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText, headersObjectToArray(e.response.headers, ','))), eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page.frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)), eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page.frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)), diff --git a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts index 8fc68d2fa2592..428585c7e351b 100644 --- a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts +++ b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts @@ -61,7 +61,7 @@ export class WKInterceptableRequest { if (event.request.postData) postDataBuffer = Buffer.from(event.request.postData, 'base64'); this.request = new network.Request(frame._page.browserContext, frame, null, redirectedFrom?.request || null, documentId, event.request.url, - resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers)); + resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers), this._wallTime); } adoptRequestFromNewProcess(newSession: WKSession, requestId: string) { diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index acc220a4b38a1..845ff30ea9616 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -397,7 +397,7 @@ export class WKPage implements PageDelegate { eventsHelper.addEventListener(this._session, 'Network.loadingFinished', e => this._onLoadingFinished(e)), eventsHelper.addEventListener(this._session, 'Network.loadingFailed', e => this._onLoadingFailed(this._session, e)), eventsHelper.addEventListener(this._session, 'Network.webSocketCreated', e => this._page.frameManager.onWebSocketCreated(e.requestId, e.url)), - eventsHelper.addEventListener(this._session, 'Network.webSocketWillSendHandshakeRequest', e => this._page.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers), e.walltime, e.timestamp)), + eventsHelper.addEventListener(this._session, 'Network.webSocketWillSendHandshakeRequest', e => this._page.frameManager.onWebSocketRequest(e.requestId, headersObjectToArray(e.request.headers), e.walltime * 1000, e.timestamp)), eventsHelper.addEventListener(this._session, 'Network.webSocketHandshakeResponseReceived', e => this._page.frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText, headersObjectToArray(e.response.headers, ',', wkSetCookieSeparator))), eventsHelper.addEventListener(this._session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page.frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)), eventsHelper.addEventListener(this._session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page.frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData, e.timestamp)), diff --git a/packages/playwright-core/src/utilsBundle.ts b/packages/playwright-core/src/utilsBundle.ts index d6a69b083d8b0..af57d4cf10f19 100644 --- a/packages/playwright-core/src/utilsBundle.ts +++ b/packages/playwright-core/src/utilsBundle.ts @@ -81,6 +81,7 @@ import * as getEastAsianWidthLibrary from 'get-east-asian-width'; export const getEastAsianWidth = getEastAsianWidthLibrary; export * as yazl from 'yazl'; +export * as yauzl from 'yauzl'; // @ts-expect-error untyped vendored module import * as gracefulFsLibrary from 'graceful-fs'; diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index f4df8b0d7c9c2..c03283cf8e64a 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -196,7 +196,7 @@ const testOptions: [string, { description: string, choices?: string[], preset?: ['--fully-parallel', { description: `Run all tests in parallel (default: false)` }], ['--global-timeout ', { description: `Maximum time this test suite can run in milliseconds (default: unlimited)` }], ['-g, --grep ', { description: `Only run tests matching this regular expression (default: ".*")` }], - ['--grep-invert ', { description: `Only run tests that do not match this regular expression` }], + ['-G, --grep-invert ', { description: `Only run tests that do not match this regular expression` }], ['--headed', { description: `Run tests in headed browsers (default: headless)` }], ['--ignore-snapshots', { description: `Ignore screenshot and snapshot expectations` }], ['--last-failed', { description: `Only re-run the failures` }], diff --git a/packages/playwright/src/worker/DEPS.list b/packages/playwright/src/worker/DEPS.list index 40fc65671b041..72ccc32a7361d 100644 --- a/packages/playwright/src/worker/DEPS.list +++ b/packages/playwright/src/worker/DEPS.list @@ -6,4 +6,5 @@ ../util.ts ../matchers/** node_modules/colors/safe +node_modules/yauzl node_modules/yazl diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index 593c67d3b9569..8a0d275dfe2a3 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import path from 'path'; import * as yazl from 'yazl'; -import * as yauzl from '@utils/third_party/yauzl'; +import * as yauzl from 'yauzl'; import { ManualPromise } from '@isomorphic/manualPromise'; import { monotonicTime } from '@isomorphic/time'; import { calculateSha1, createGuid } from '@utils/crypto'; diff --git a/packages/utils/DEPS.list b/packages/utils/DEPS.list index aa25a66a8a292..0834f57c8e073 100644 --- a/packages/utils/DEPS.list +++ b/packages/utils/DEPS.list @@ -10,6 +10,7 @@ node_modules/pngjs node_modules/proxy-from-env node_modules/socks-proxy-agent node_modules/ws +node_modules/yauzl node_modules/yazl [comparators.ts] diff --git a/packages/utils/socksProxy.ts b/packages/utils/socksProxy.ts index 03ca66e618f49..db629b4a12989 100644 --- a/packages/utils/socksProxy.ts +++ b/packages/utils/socksProxy.ts @@ -294,6 +294,11 @@ function hexToNumber(hex: string): number { } function ipToSocksAddress(address: string): number[] { + // Node.js may return IPv4-mapped IPv6 addresses (e.g. "::ffff:10.0.0.1") from + // socket.localAddress on dual-stack systems. Unwrap them to plain IPv4. + const ipv4Mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(address); + if (ipv4Mapped) + address = ipv4Mapped[1]; if (net.isIPv4(address)) { return [ 0x01, // IPv4 diff --git a/packages/utils/third_party/extractZip.ts b/packages/utils/third_party/extractZip.ts index 0e8bb41181b74..1dc0d27253042 100644 --- a/packages/utils/third_party/extractZip.ts +++ b/packages/utils/third_party/extractZip.ts @@ -31,12 +31,12 @@ import { promisify } from 'util'; import debugPkg from 'debug'; import getStream from 'get-stream'; -import yauzl from './yauzl'; -import type { Entry, Options as YauzlOptions, ZipFile } from './yauzl'; +import yauzl from 'yauzl'; +import type { Entry, ZipFile } from 'yauzl'; const debug = debugPkg('extract-zip'); -const openZip = promisify(yauzl.open); +const openZip = promisify(yauzl.open); const pipeline = promisify(stream.pipeline); export interface Options { diff --git a/packages/utils/third_party/yauzl/LICENSE b/packages/utils/third_party/yauzl/LICENSE deleted file mode 100644 index 65412d42edd0b..0000000000000 --- a/packages/utils/third_party/yauzl/LICENSE +++ /dev/null @@ -1,134 +0,0 @@ -This directory contains code vendored from several MIT-licensed projects. - -================================================================================ -yauzl (https://github.com/thejoshwolfe/yauzl) -================================================================================ - -The MIT License (MIT) - -Copyright (c) 2014 Josh Wolfe - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -================================================================================ -node-fd-slicer (https://github.com/andrewrk/node-fd-slicer) — embedded as -fd-slicer.js, with additional fix from https://github.com/thejoshwolfe/yauzl/pull/168 -================================================================================ - -The MIT License (Expat) - -Copyright (c) 2014 Andrew Kelley - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation files -(the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -================================================================================ -pend (https://github.com/andrewrk/node-pend) -================================================================================ - -The MIT License (Expat) - -Copyright (c) 2014 Andrew Kelley - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation files -(the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -================================================================================ -buffer-crc32 (https://github.com/brianloveswords/buffer-crc32) -================================================================================ - -The MIT License - -Copyright (c) 2013 Brian J. Brennan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -================================================================================ -DefinitelyTyped @types/yauzl — basis for index.d.ts -================================================================================ - -MIT License - -Copyright (c) Microsoft Corporation. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/utils/third_party/yauzl/buffer-crc32.js b/packages/utils/third_party/yauzl/buffer-crc32.js deleted file mode 100644 index 534590d76a63e..0000000000000 --- a/packages/utils/third_party/yauzl/buffer-crc32.js +++ /dev/null @@ -1,114 +0,0 @@ -// Vendored from https://github.com/brianloveswords/buffer-crc32 at v0.2.13 under the MIT License. -// See LICENSE for the full text. - -var Buffer = require('buffer').Buffer; - -var CRC_TABLE = [ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, - 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, - 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, - 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, - 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, - 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, - 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, - 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, - 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, - 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, - 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, - 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, - 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, - 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, - 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, - 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, - 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, - 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, - 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, - 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, - 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, - 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, - 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, - 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, - 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, - 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, - 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, - 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, - 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, - 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, - 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, - 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, - 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, - 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, - 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, - 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, - 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, - 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, - 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, - 0x2d02ef8d -]; - -if (typeof Int32Array !== 'undefined') { - CRC_TABLE = new Int32Array(CRC_TABLE); -} - -function ensureBuffer(input) { - if (Buffer.isBuffer(input)) { - return input; - } - - var hasNewBufferAPI = - typeof Buffer.alloc === "function" && - typeof Buffer.from === "function"; - - if (typeof input === "number") { - return hasNewBufferAPI ? Buffer.alloc(input) : new Buffer(input); - } - else if (typeof input === "string") { - return hasNewBufferAPI ? Buffer.from(input) : new Buffer(input); - } - else { - throw new Error("input must be buffer, number, or string, received " + - typeof input); - } -} - -function bufferizeInt(num) { - var tmp = ensureBuffer(4); - tmp.writeInt32BE(num, 0); - return tmp; -} - -function _crc32(buf, previous) { - buf = ensureBuffer(buf); - if (Buffer.isBuffer(previous)) { - previous = previous.readUInt32BE(0); - } - var crc = ~~previous ^ -1; - for (var n = 0; n < buf.length; n++) { - crc = CRC_TABLE[(crc ^ buf[n]) & 0xff] ^ (crc >>> 8); - } - return (crc ^ -1); -} - -function crc32() { - return bufferizeInt(_crc32.apply(null, arguments)); -} -crc32.signed = function () { - return _crc32.apply(null, arguments); -}; -crc32.unsigned = function () { - return _crc32.apply(null, arguments) >>> 0; -}; - -module.exports = crc32; diff --git a/packages/utils/third_party/yauzl/fd-slicer.js b/packages/utils/third_party/yauzl/fd-slicer.js deleted file mode 100644 index 2888ea474fe7e..0000000000000 --- a/packages/utils/third_party/yauzl/fd-slicer.js +++ /dev/null @@ -1,330 +0,0 @@ -// This was adapted from https://github.com/andrewrk/node-fd-slicer by Andrew Kelley under the MIT License. -// Originally vendored from https://github.com/thejoshwolfe/yauzl at 3.2.1. -// Modified to apply the destroy() lifecycle fix from -// https://github.com/thejoshwolfe/yauzl/pull/168 (HoJeong Go) so async iteration -// over `openReadStream` properly emits 'close' on modern Node. -var fs = require('fs'); -var util = require('util'); -var stream = require('stream'); -var Readable = stream.Readable; -var Writable = stream.Writable; -var PassThrough = stream.PassThrough; -var Pend = require('./pend'); -var EventEmitter = require('events').EventEmitter; - -exports.createFromBuffer = createFromBuffer; -exports.createFromFd = createFromFd; -exports.BufferSlicer = BufferSlicer; -exports.FdSlicer = FdSlicer; - -util.inherits(FdSlicer, EventEmitter); -function FdSlicer(fd, options) { - options = options || {}; - EventEmitter.call(this); - - this.fd = fd; - this.pend = new Pend(); - this.pend.max = 1; - this.refCount = 0; - this.autoClose = !!options.autoClose; -} - -FdSlicer.prototype.read = function(buffer, offset, length, position, callback) { - var self = this; - self.pend.go(function(cb) { - fs.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer) { - cb(); - callback(err, bytesRead, buffer); - }); - }); -}; - -FdSlicer.prototype.write = function(buffer, offset, length, position, callback) { - var self = this; - self.pend.go(function(cb) { - fs.write(self.fd, buffer, offset, length, position, function(err, written, buffer) { - cb(); - callback(err, written, buffer); - }); - }); -}; - -FdSlicer.prototype.createReadStream = function(options) { - return new ReadStream(this, options); -}; - -FdSlicer.prototype.createWriteStream = function(options) { - return new WriteStream(this, options); -}; - -FdSlicer.prototype.ref = function() { - this.refCount += 1; -}; - -FdSlicer.prototype.unref = function() { - var self = this; - self.refCount -= 1; - - if (self.refCount > 0) return; - if (self.refCount < 0) throw new Error("invalid unref"); - - if (self.autoClose) { - fs.close(self.fd, onCloseDone); - } - - function onCloseDone(err) { - if (err) { - self.emit('error', err); - } else { - self.emit('close'); - } - } -}; - -util.inherits(ReadStream, Readable); -function ReadStream(context, options) { - options = options || {}; - Readable.call(this, options); - - this.context = context; - this.context.ref(); - - this.start = options.start || 0; - this.endOffset = options.end; - this.pos = this.start; - this._destroyed = false; -} - -ReadStream.prototype._read = function(n) { - var self = this; - if (self._destroyed) return; - - var toRead = Math.min(self._readableState.highWaterMark, n); - if (self.endOffset != null) { - toRead = Math.min(toRead, self.endOffset - self.pos); - } - if (toRead <= 0) { - self._destroyed = true; - self.push(null); - self.context.unref(); - return; - } - self.context.pend.go(function(cb) { - if (self._destroyed) return cb(); - var buffer = Buffer.allocUnsafe(toRead); - fs.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) { - if (self._destroyed) return cb(); - if (err) { - self.destroy(err); - } else if (bytesRead === 0) { - self._destroyed = true; - self.push(null); - self.context.unref(); - } else { - self.pos += bytesRead; - self.push(buffer.slice(0, bytesRead)); - } - cb(); - }); - }); -}; - -ReadStream.prototype.destroy = function(err) { - if (err == null && !this.readableEnded) { - err = new Error("stream destroyed"); - } - return Readable.prototype.destroy.call(this, err); -}; - -ReadStream.prototype._destroy = function(err, cb) { - if (!this._destroyed) { - this._destroyed = true; - this.context.unref(); - } - cb(err); -}; - -util.inherits(WriteStream, Writable); -function WriteStream(context, options) { - options = options || {}; - Writable.call(this, options); - - this.context = context; - this.context.ref(); - - this.start = options.start || 0; - this.endOffset = (options.end == null) ? Infinity : +options.end; - this.bytesWritten = 0; - this.pos = this.start; - this._destroyed = false; - - this.on('finish', this.destroy.bind(this)); -} - -WriteStream.prototype._write = function(buffer, encoding, callback) { - var self = this; - if (self._destroyed) return; - - if (self.pos + buffer.length > self.endOffset) { - var err = new Error("maximum file length exceeded"); - err.code = 'ETOOBIG'; - self.destroy(); - callback(err); - return; - } - self.context.pend.go(function(cb) { - if (self._destroyed) return cb(); - fs.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err, bytes) { - if (err) { - self.destroy(); - cb(); - callback(err); - } else { - self.bytesWritten += bytes; - self.pos += bytes; - self.emit('progress'); - cb(); - callback(); - } - }); - }); -}; - -WriteStream.prototype._destroy = function(err, cb) { - if (!this._destroyed) { - this._destroyed = true; - this.context.unref(); - } - cb(err); -}; - -util.inherits(BufferSlicer, EventEmitter); -function BufferSlicer(buffer, options) { - EventEmitter.call(this); - - options = options || {}; - this.refCount = 0; - this.buffer = buffer; - this.maxChunkSize = options.maxChunkSize || Number.MAX_SAFE_INTEGER; -} - -BufferSlicer.prototype.read = function(buffer, offset, length, position, callback) { - if (!(0 <= offset && offset <= buffer.length)) throw new RangeError("offset outside buffer: 0 <= " + offset + " <= " + buffer.length); - if (position < 0) throw new RangeError("position is negative: " + position); - if (offset + length > buffer.length) { - // The caller's buffer can't hold all the bytes they're trying to read. - // Clamp the length instead of giving an error. - // The callback will be informed of fewer than expected bytes written. - length = buffer.length - offset; - } - if (position + length > this.buffer.length) { - // Clamp any attempt to read past the end of the source buffer. - length = this.buffer.length - position; - } - if (length <= 0) { - // After any clamping, we're fully out of bounds or otherwise have nothing to do. - // This isn't an error; it's just zero bytes written. - setImmediate(function() { - callback(null, 0); - }); - return; - } - this.buffer.copy(buffer, offset, position, position + length); - setImmediate(function() { - callback(null, length); - }); -}; - -BufferSlicer.prototype.write = function(buffer, offset, length, position, callback) { - buffer.copy(this.buffer, position, offset, offset + length); - setImmediate(function() { - callback(null, length, buffer); - }); -}; - -BufferSlicer.prototype.createReadStream = function(options) { - options = options || {}; - var readStream = new PassThrough(options); - readStream._destroyed = false; - readStream.start = options.start || 0; - readStream.endOffset = options.end; - // by the time this function returns, we'll be done. - readStream.pos = readStream.endOffset || this.buffer.length; - - // respect the maxChunkSize option to slice up the chunk into smaller pieces. - var entireSlice = this.buffer.slice(readStream.start, readStream.pos); - var offset = 0; - while (true) { - var nextOffset = offset + this.maxChunkSize; - if (nextOffset >= entireSlice.length) { - // last chunk - if (offset < entireSlice.length) { - readStream.write(entireSlice.slice(offset, entireSlice.length)); - } - break; - } - readStream.write(entireSlice.slice(offset, nextOffset)); - offset = nextOffset; - } - - readStream.end(); - readStream._destroy = function(err, cb) { - readStream._destroyed = true; - PassThrough.prototype._destroy.call(readStream, err, cb); - }; - return readStream; -}; - -BufferSlicer.prototype.createWriteStream = function(options) { - var bufferSlicer = this; - options = options || {}; - var writeStream = new Writable(options); - writeStream.start = options.start || 0; - writeStream.endOffset = (options.end == null) ? this.buffer.length : +options.end; - writeStream.bytesWritten = 0; - writeStream.pos = writeStream.start; - writeStream._destroyed = false; - writeStream._write = function(buffer, encoding, callback) { - if (writeStream._destroyed) return; - - var end = writeStream.pos + buffer.length; - if (end > writeStream.endOffset) { - var err = new Error("maximum file length exceeded"); - err.code = 'ETOOBIG'; - writeStream._destroyed = true; - callback(err); - return; - } - buffer.copy(bufferSlicer.buffer, writeStream.pos, 0, buffer.length); - - writeStream.bytesWritten += buffer.length; - writeStream.pos = end; - writeStream.emit('progress'); - callback(); - }; - writeStream._destroy = function(err, cb) { - writeStream._destroyed = true; - cb(err); - }; - return writeStream; -}; - -BufferSlicer.prototype.ref = function() { - this.refCount += 1; -}; - -BufferSlicer.prototype.unref = function() { - this.refCount -= 1; - - if (this.refCount < 0) { - throw new Error("invalid unref"); - } -}; - -function createFromBuffer(buffer, options) { - return new BufferSlicer(buffer, options); -} - -function createFromFd(fd, options) { - return new FdSlicer(fd, options); -} diff --git a/packages/utils/third_party/yauzl/index.d.ts b/packages/utils/third_party/yauzl/index.d.ts deleted file mode 100644 index 0b49b62f0d5c9..0000000000000 --- a/packages/utils/third_party/yauzl/index.d.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Adapted from https://www.npmjs.com/package/@types/yauzl (DefinitelyTyped, MIT). -/// - -import { EventEmitter } from 'events'; -import { Readable } from 'stream'; - -export abstract class RandomAccessReader extends EventEmitter { - _readStreamForRange(start: number, end: number): void; - createReadStream(options: { start: number; end: number }): void; - read(buffer: Buffer, offset: number, length: number, position: number, callback: (err: Error | null) => void): void; - close(callback: (err: Error | null) => void): void; -} - -export class Entry { - comment: string; - compressedSize: number; - compressionMethod: number; - crc32: number; - externalFileAttributes: number; - extraFieldLength: number; - extraFields: Array<{ id: number; data: Buffer }>; - fileCommentLength: number; - fileName: string; - fileNameLength: number; - generalPurposeBitFlag: number; - internalFileAttributes: number; - lastModFileDate: number; - lastModFileTime: number; - relativeOffsetOfLocalHeader: number; - uncompressedSize: number; - versionMadeBy: number; - versionNeededToExtract: number; - - getLastModDate(): Date; - isEncrypted(): boolean; - isCompressed(): boolean; -} - -export interface ZipFileOptions { - decompress: boolean | null; - decrypt: boolean | null; - start: number | null; - end: number | null; -} - -export class ZipFile extends EventEmitter { - autoClose: boolean; - comment: string; - decodeStrings: boolean; - emittedError: boolean; - entriesRead: number; - entryCount: number; - fileSize: number; - isOpen: boolean; - lazyEntries: boolean; - readEntryCursor: boolean; - validateEntrySizes: boolean; - - constructor( - reader: RandomAccessReader, - centralDirectoryOffset: number, - fileSize: number, - entryCount: number, - comment: string, - autoClose: boolean, - lazyEntries: boolean, - decodeStrings: boolean, - validateEntrySizes: boolean, - ); - - openReadStream( - entry: Entry, - options: ZipFileOptions, - callback: (err: Error | null, stream: Readable) => void, - ): void; - openReadStream(entry: Entry, callback: (err: Error | null, stream: Readable) => void): void; - close(): void; - readEntry(): void; -} - -export interface Options { - autoClose?: boolean | undefined; - lazyEntries?: boolean | undefined; - decodeStrings?: boolean | undefined; - validateEntrySizes?: boolean | undefined; - strictFileNames?: boolean | undefined; -} - -export function open(path: string, options: Options, callback?: (err: Error | null, zipfile: ZipFile) => void): void; -export function open(path: string, callback?: (err: Error | null, zipfile: ZipFile) => void): void; -export function fromFd(fd: number, options: Options, callback?: (err: Error | null, zipfile: ZipFile) => void): void; -export function fromFd(fd: number, callback?: (err: Error | null, zipfile: ZipFile) => void): void; -export function fromBuffer( - buffer: Buffer, - options: Options, - callback?: (err: Error | null, zipfile: ZipFile) => void, -): void; -export function fromBuffer(buffer: Buffer, callback?: (err: Error | null, zipfile: ZipFile) => void): void; -export function fromRandomAccessReader( - reader: RandomAccessReader, - totalSize: number, - options: Options, - callback: (err: Error | null, zipfile: ZipFile) => void, -): void; -export function fromRandomAccessReader( - reader: RandomAccessReader, - totalSize: number, - callback: (err: Error | null, zipfile: ZipFile) => void, -): void; -export function dosDateTimeToDate(date: number, time: number): Date; -export function validateFileName(fileName: string): string | null; - -declare const yauzl: { - open: typeof open; - fromFd: typeof fromFd; - fromBuffer: typeof fromBuffer; - fromRandomAccessReader: typeof fromRandomAccessReader; - dosDateTimeToDate: typeof dosDateTimeToDate; - validateFileName: typeof validateFileName; - ZipFile: typeof ZipFile; - Entry: typeof Entry; - RandomAccessReader: typeof RandomAccessReader; -}; -export default yauzl; diff --git a/packages/utils/third_party/yauzl/index.js b/packages/utils/third_party/yauzl/index.js deleted file mode 100644 index 3661c7654080b..0000000000000 --- a/packages/utils/third_party/yauzl/index.js +++ /dev/null @@ -1,951 +0,0 @@ -// Vendored from https://github.com/thejoshwolfe/yauzl at v3.2.1 under the MIT License. -// See LICENSE for the full text. -// -// Local modifications: -// - require('./fd-slicer'): unchanged path; fd-slicer.js carries the fix from -// https://github.com/thejoshwolfe/yauzl/pull/168. -// - require('buffer-crc32'): rewritten to ./buffer-crc32 (inlined sibling). - -var fs = require("fs"); -var zlib = require("zlib"); -var fd_slicer = require("./fd-slicer"); -var crc32 = require("./buffer-crc32"); -var util = require("util"); -var EventEmitter = require("events").EventEmitter; -var Transform = require("stream").Transform; -var PassThrough = require("stream").PassThrough; -var Writable = require("stream").Writable; - -exports.open = open; -exports.fromFd = fromFd; -exports.fromBuffer = fromBuffer; -exports.fromRandomAccessReader = fromRandomAccessReader; -exports.dosDateTimeToDate = dosDateTimeToDate; -exports.getFileNameLowLevel = getFileNameLowLevel; -exports.validateFileName = validateFileName; -exports.parseExtraFields = parseExtraFields; -exports.ZipFile = ZipFile; -exports.Entry = Entry; -exports.LocalFileHeader = LocalFileHeader; -exports.RandomAccessReader = RandomAccessReader; - -function open(path, options, callback) { - if (typeof options === "function") { - callback = options; - options = null; - } - if (options == null) options = {}; - if (options.autoClose == null) options.autoClose = true; - if (options.lazyEntries == null) options.lazyEntries = false; - if (options.decodeStrings == null) options.decodeStrings = true; - if (options.validateEntrySizes == null) options.validateEntrySizes = true; - if (options.strictFileNames == null) options.strictFileNames = false; - if (callback == null) callback = defaultCallback; - fs.open(path, "r", function(err, fd) { - if (err) return callback(err); - fromFd(fd, options, function(err, zipfile) { - if (err) fs.close(fd, defaultCallback); - callback(err, zipfile); - }); - }); -} - -function fromFd(fd, options, callback) { - if (typeof options === "function") { - callback = options; - options = null; - } - if (options == null) options = {}; - if (options.autoClose == null) options.autoClose = false; - if (options.lazyEntries == null) options.lazyEntries = false; - if (options.decodeStrings == null) options.decodeStrings = true; - if (options.validateEntrySizes == null) options.validateEntrySizes = true; - if (options.strictFileNames == null) options.strictFileNames = false; - if (callback == null) callback = defaultCallback; - fs.fstat(fd, function(err, stats) { - if (err) return callback(err); - var reader = fd_slicer.createFromFd(fd, {autoClose: true}); - fromRandomAccessReader(reader, stats.size, options, callback); - }); -} - -function fromBuffer(buffer, options, callback) { - if (typeof options === "function") { - callback = options; - options = null; - } - if (options == null) options = {}; - options.autoClose = false; - if (options.lazyEntries == null) options.lazyEntries = false; - if (options.decodeStrings == null) options.decodeStrings = true; - if (options.validateEntrySizes == null) options.validateEntrySizes = true; - if (options.strictFileNames == null) options.strictFileNames = false; - // limit the max chunk size. see https://github.com/thejoshwolfe/yauzl/issues/87 - var reader = fd_slicer.createFromBuffer(buffer, {maxChunkSize: 0x10000}); - fromRandomAccessReader(reader, buffer.length, options, callback); -} - -function fromRandomAccessReader(reader, totalSize, options, callback) { - if (typeof options === "function") { - callback = options; - options = null; - } - if (options == null) options = {}; - if (options.autoClose == null) options.autoClose = true; - if (options.lazyEntries == null) options.lazyEntries = false; - if (options.decodeStrings == null) options.decodeStrings = true; - var decodeStrings = !!options.decodeStrings; - if (options.validateEntrySizes == null) options.validateEntrySizes = true; - if (options.strictFileNames == null) options.strictFileNames = false; - if (callback == null) callback = defaultCallback; - if (typeof totalSize !== "number") throw new Error("expected totalSize parameter to be a number"); - if (totalSize > Number.MAX_SAFE_INTEGER) { - throw new Error("zip file too large. only file sizes up to 2^52 are supported due to JavaScript's Number type being an IEEE 754 double."); - } - - // the matching unref() call is in zipfile.close() - reader.ref(); - - // eocdr means End of Central Directory Record. - // search backwards for the eocdr signature. - // the last field of the eocdr is a variable-length comment. - // the comment size is encoded in a 2-byte field in the eocdr, which we can't find without trudging backwards through the comment to find it. - // as a consequence of this design decision, it's possible to have ambiguous zip file metadata if a coherent eocdr was in the comment. - // we search backwards for a eocdr signature, and hope that whoever made the zip file was smart enough to forbid the eocdr signature in the comment. - var eocdrWithoutCommentSize = 22; - var zip64EocdlSize = 20; // Zip64 end of central directory locator - var maxCommentSize = 0xffff; // 2-byte size - var bufferSize = Math.min(zip64EocdlSize + eocdrWithoutCommentSize + maxCommentSize, totalSize); - var buffer = newBuffer(bufferSize); - var bufferReadStart = totalSize - buffer.length; - readAndAssertNoEof(reader, buffer, 0, bufferSize, bufferReadStart, function(err) { - if (err) return callback(err); - for (var i = bufferSize - eocdrWithoutCommentSize; i >= 0; i -= 1) { - if (buffer.readUInt32LE(i) !== 0x06054b50) continue; - // found eocdr - var eocdrBuffer = buffer.subarray(i); - - // 0 - End of central directory signature = 0x06054b50 - // 4 - Number of this disk - var diskNumber = eocdrBuffer.readUInt16LE(4); - // 6 - Disk where central directory starts - // 8 - Number of central directory records on this disk - // 10 - Total number of central directory records - var entryCount = eocdrBuffer.readUInt16LE(10); - // 12 - Size of central directory (bytes) - // 16 - Offset of start of central directory, relative to start of archive - var centralDirectoryOffset = eocdrBuffer.readUInt32LE(16); - // 20 - Comment length - var commentLength = eocdrBuffer.readUInt16LE(20); - var expectedCommentLength = eocdrBuffer.length - eocdrWithoutCommentSize; - if (commentLength !== expectedCommentLength) { - return callback(new Error("Invalid comment length. Expected: " + expectedCommentLength + ". Found: " + commentLength + ". Are there extra bytes at the end of the file? Or is the end of central dir signature `PK☺☻` in the comment?")); - } - // 22 - Comment - // the encoding is always cp437. - var comment = decodeStrings ? decodeBuffer(eocdrBuffer.subarray(22), false) - : eocdrBuffer.subarray(22); - - // Look for a Zip64 end of central directory locator - if (i - zip64EocdlSize >= 0 && buffer.readUInt32LE(i - zip64EocdlSize) === 0x07064b50) { - // ZIP64 format - var zip64EocdlBuffer = buffer.subarray(i - zip64EocdlSize, i - zip64EocdlSize + zip64EocdlSize); - // 0 - zip64 end of central dir locator signature = 0x07064b50 - // 4 - number of the disk with the start of the zip64 end of central directory - // 8 - relative offset of the zip64 end of central directory record - var zip64EocdrOffset = readUInt64LE(zip64EocdlBuffer, 8); - // 16 - total number of disks - - // ZIP64 end of central directory record - var zip64EocdrBuffer = newBuffer(56); - return readAndAssertNoEof(reader, zip64EocdrBuffer, 0, zip64EocdrBuffer.length, zip64EocdrOffset, function(err) { - if (err) return callback(err); - - // 0 - zip64 end of central dir signature 4 bytes (0x06064b50) - if (zip64EocdrBuffer.readUInt32LE(0) !== 0x06064b50) { - return callback(new Error("invalid zip64 end of central directory record signature")); - } - // 4 - size of zip64 end of central directory record 8 bytes - // 12 - version made by 2 bytes - // 14 - version needed to extract 2 bytes - // 16 - number of this disk 4 bytes - diskNumber = zip64EocdrBuffer.readUInt32LE(16); - if (diskNumber !== 0) { - // Check this only after zip64 overrides. See #118. - return callback(new Error("multi-disk zip files are not supported: found disk number: " + diskNumber)); - } - // 20 - number of the disk with the start of the central directory 4 bytes - // 24 - total number of entries in the central directory on this disk 8 bytes - // 32 - total number of entries in the central directory 8 bytes - entryCount = readUInt64LE(zip64EocdrBuffer, 32); - // 40 - size of the central directory 8 bytes - // 48 - offset of start of central directory with respect to the starting disk number 8 bytes - centralDirectoryOffset = readUInt64LE(zip64EocdrBuffer, 48); - // 56 - zip64 extensible data sector (variable size) - return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes, options.strictFileNames)); - }); - } - - // Not ZIP64 format - if (diskNumber !== 0) { - return callback(new Error("multi-disk zip files are not supported: found disk number: " + diskNumber)); - } - return callback(null, new ZipFile(reader, centralDirectoryOffset, totalSize, entryCount, comment, options.autoClose, options.lazyEntries, decodeStrings, options.validateEntrySizes, options.strictFileNames)); - - } - - // Not a zip file. - callback(new Error("End of central directory record signature not found. Either not a zip file, or file is truncated.")); - }); -} - -util.inherits(ZipFile, EventEmitter); -function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes, strictFileNames) { - var self = this; - EventEmitter.call(self); - self.reader = reader; - // forward close events - self.reader.on("error", function(err) { - // error closing the fd - emitError(self, err); - }); - self.reader.once("close", function() { - self.emit("close"); - }); - self.readEntryCursor = centralDirectoryOffset; - self.fileSize = fileSize; - self.entryCount = entryCount; - self.comment = comment; - self.entriesRead = 0; - self.autoClose = !!autoClose; - self.lazyEntries = !!lazyEntries; - self.decodeStrings = !!decodeStrings; - self.validateEntrySizes = !!validateEntrySizes; - self.strictFileNames = !!strictFileNames; - self.isOpen = true; - self.emittedError = false; - - if (!self.lazyEntries) self._readEntry(); -} -ZipFile.prototype.close = function() { - if (!this.isOpen) return; - this.isOpen = false; - this.reader.unref(); -}; - -function emitErrorAndAutoClose(self, err) { - if (self.autoClose) self.close(); - emitError(self, err); -} -function emitError(self, err) { - if (self.emittedError) return; - self.emittedError = true; - self.emit("error", err); -} - -ZipFile.prototype.readEntry = function() { - if (!this.lazyEntries) throw new Error("readEntry() called without lazyEntries:true"); - this._readEntry(); -}; -ZipFile.prototype._readEntry = function() { - var self = this; - if (self.entryCount === self.entriesRead) { - // done with metadata - setImmediate(function() { - if (self.autoClose) self.close(); - if (self.emittedError) return; - self.emit("end"); - }); - return; - } - if (self.emittedError) return; - var buffer = newBuffer(46); - readAndAssertNoEof(self.reader, buffer, 0, buffer.length, self.readEntryCursor, function(err) { - if (err) return emitErrorAndAutoClose(self, err); - if (self.emittedError) return; - var entry = new Entry(); - // 0 - Central directory file header signature - var signature = buffer.readUInt32LE(0); - if (signature !== 0x02014b50) return emitErrorAndAutoClose(self, new Error("invalid central directory file header signature: 0x" + signature.toString(16))); - // 4 - Version made by - entry.versionMadeBy = buffer.readUInt16LE(4); - // 6 - Version needed to extract (minimum) - entry.versionNeededToExtract = buffer.readUInt16LE(6); - // 8 - General purpose bit flag - entry.generalPurposeBitFlag = buffer.readUInt16LE(8); - // 10 - Compression method - entry.compressionMethod = buffer.readUInt16LE(10); - // 12 - File last modification time - entry.lastModFileTime = buffer.readUInt16LE(12); - // 14 - File last modification date - entry.lastModFileDate = buffer.readUInt16LE(14); - // 16 - CRC-32 - entry.crc32 = buffer.readUInt32LE(16); - // 20 - Compressed size - entry.compressedSize = buffer.readUInt32LE(20); - // 24 - Uncompressed size - entry.uncompressedSize = buffer.readUInt32LE(24); - // 28 - File name length (n) - entry.fileNameLength = buffer.readUInt16LE(28); - // 30 - Extra field length (m) - entry.extraFieldLength = buffer.readUInt16LE(30); - // 32 - File comment length (k) - entry.fileCommentLength = buffer.readUInt16LE(32); - // 34 - Disk number where file starts - // 36 - Internal file attributes - entry.internalFileAttributes = buffer.readUInt16LE(36); - // 38 - External file attributes - entry.externalFileAttributes = buffer.readUInt32LE(38); - // 42 - Relative offset of local file header - entry.relativeOffsetOfLocalHeader = buffer.readUInt32LE(42); - - if (entry.generalPurposeBitFlag & 0x40) return emitErrorAndAutoClose(self, new Error("strong encryption is not supported")); - - self.readEntryCursor += 46; - - buffer = newBuffer(entry.fileNameLength + entry.extraFieldLength + entry.fileCommentLength); - readAndAssertNoEof(self.reader, buffer, 0, buffer.length, self.readEntryCursor, function(err) { - if (err) return emitErrorAndAutoClose(self, err); - if (self.emittedError) return; - // 46 - File name - entry.fileNameRaw = buffer.subarray(0, entry.fileNameLength); - // 46+n - Extra field - var fileCommentStart = entry.fileNameLength + entry.extraFieldLength; - entry.extraFieldRaw = buffer.subarray(entry.fileNameLength, fileCommentStart); - // 46+n+m - File comment - entry.fileCommentRaw = buffer.subarray(fileCommentStart, fileCommentStart + entry.fileCommentLength); - - // Parse the extra fields, which we need for processing other fields. - try { - entry.extraFields = parseExtraFields(entry.extraFieldRaw); - } catch (err) { - return emitErrorAndAutoClose(self, err); - } - - // Interpret strings according to bit flags, extra fields, and options. - if (self.decodeStrings) { - var isUtf8 = (entry.generalPurposeBitFlag & 0x800) !== 0; - entry.fileComment = decodeBuffer(entry.fileCommentRaw, isUtf8); - entry.fileName = getFileNameLowLevel(entry.generalPurposeBitFlag, entry.fileNameRaw, entry.extraFields, self.strictFileNames); - var errorMessage = validateFileName(entry.fileName); - if (errorMessage != null) return emitErrorAndAutoClose(self, new Error(errorMessage)); - } else { - entry.fileComment = entry.fileCommentRaw; - entry.fileName = entry.fileNameRaw; - } - // Maintain API compatibility. See https://github.com/thejoshwolfe/yauzl/issues/47 - entry.comment = entry.fileComment; - - self.readEntryCursor += buffer.length; - self.entriesRead += 1; - - // Check for the Zip64 Extended Information Extra Field. - for (var i = 0; i < entry.extraFields.length; i++) { - var extraField = entry.extraFields[i]; - if (extraField.id !== 0x0001) continue; - // Found it. - - var zip64EiefBuffer = extraField.data; - var index = 0; - // 0 - Original Size 8 bytes - if (entry.uncompressedSize === 0xffffffff) { - if (index + 8 > zip64EiefBuffer.length) { - return emitErrorAndAutoClose(self, new Error("zip64 extended information extra field does not include uncompressed size")); - } - entry.uncompressedSize = readUInt64LE(zip64EiefBuffer, index); - index += 8; - } - // 8 - Compressed Size 8 bytes - if (entry.compressedSize === 0xffffffff) { - if (index + 8 > zip64EiefBuffer.length) { - return emitErrorAndAutoClose(self, new Error("zip64 extended information extra field does not include compressed size")); - } - entry.compressedSize = readUInt64LE(zip64EiefBuffer, index); - index += 8; - } - // 16 - Relative Header Offset 8 bytes - if (entry.relativeOffsetOfLocalHeader === 0xffffffff) { - if (index + 8 > zip64EiefBuffer.length) { - return emitErrorAndAutoClose(self, new Error("zip64 extended information extra field does not include relative header offset")); - } - entry.relativeOffsetOfLocalHeader = readUInt64LE(zip64EiefBuffer, index); - index += 8; - } - // 24 - Disk Start Number 4 bytes - - break; - } - - // validate file size - if (self.validateEntrySizes && entry.compressionMethod === 0) { - var expectedCompressedSize = entry.uncompressedSize; - if (entry.isEncrypted()) { - // traditional encryption prefixes the file data with a header - expectedCompressedSize += 12; - } - if (entry.compressedSize !== expectedCompressedSize) { - var msg = "compressed/uncompressed size mismatch for stored file: " + entry.compressedSize + " != " + entry.uncompressedSize; - return emitErrorAndAutoClose(self, new Error(msg)); - } - } - - self.emit("entry", entry); - - if (!self.lazyEntries) self._readEntry(); - }); - }); -}; - -ZipFile.prototype.openReadStream = function(entry, options, callback) { - var self = this; - // parameter validation - var relativeStart = 0; - var relativeEnd = entry.compressedSize; - if (callback == null) { - callback = options; - options = null; - } - if (options == null) { - options = {}; - } else { - // validate options that the caller has no excuse to get wrong - if (options.decrypt != null) { - if (!entry.isEncrypted()) { - throw new Error("options.decrypt can only be specified for encrypted entries"); - } - if (options.decrypt !== false) throw new Error("invalid options.decrypt value: " + options.decrypt); - if (entry.isCompressed()) { - if (options.decompress !== false) throw new Error("entry is encrypted and compressed, and options.decompress !== false"); - } - } - if (options.decompress != null) { - if (!entry.isCompressed()) { - throw new Error("options.decompress can only be specified for compressed entries"); - } - if (!(options.decompress === false || options.decompress === true)) { - throw new Error("invalid options.decompress value: " + options.decompress); - } - } - if (options.start != null || options.end != null) { - if (entry.isCompressed() && options.decompress !== false) { - throw new Error("start/end range not allowed for compressed entry without options.decompress === false"); - } - if (entry.isEncrypted() && options.decrypt !== false) { - throw new Error("start/end range not allowed for encrypted entry without options.decrypt === false"); - } - } - if (options.start != null) { - relativeStart = options.start; - if (relativeStart < 0) throw new Error("options.start < 0"); - if (relativeStart > entry.compressedSize) throw new Error("options.start > entry.compressedSize"); - } - if (options.end != null) { - relativeEnd = options.end; - if (relativeEnd < 0) throw new Error("options.end < 0"); - if (relativeEnd > entry.compressedSize) throw new Error("options.end > entry.compressedSize"); - if (relativeEnd < relativeStart) throw new Error("options.end < options.start"); - } - } - // any further errors can either be caused by the zipfile, - // or were introduced in a minor version of yauzl, - // so should be passed to the client rather than thrown. - if (!self.isOpen) return callback(new Error("closed")); - if (entry.isEncrypted()) { - if (options.decrypt !== false) return callback(new Error("entry is encrypted, and options.decrypt !== false")); - } - var decompress; - if (entry.compressionMethod === 0) { - // 0 - The file is stored (no compression) - decompress = false; - } else if (entry.compressionMethod === 8) { - // 8 - The file is Deflated - decompress = options.decompress != null ? options.decompress : true; - } else { - return callback(new Error("unsupported compression method: " + entry.compressionMethod)); - } - - self.readLocalFileHeader(entry, {minimal: true}, function(err, localFileHeader) { - if (err) return callback(err); - self.openReadStreamLowLevel( - localFileHeader.fileDataStart, entry.compressedSize, - relativeStart, relativeEnd, - decompress, entry.uncompressedSize, - callback); - }); -}; - -ZipFile.prototype.openReadStreamLowLevel = function(fileDataStart, compressedSize, relativeStart, relativeEnd, decompress, uncompressedSize, callback) { - var self = this; - - var fileDataEnd = fileDataStart + compressedSize; - var readStream = self.reader.createReadStream({ - start: fileDataStart + relativeStart, - end: fileDataStart + relativeEnd, - }); - var endpointStream = readStream; - if (decompress) { - var destroyed = false; - var inflateFilter = zlib.createInflateRaw(); - readStream.on("error", function(err) { - // setImmediate here because errors can be emitted during the first call to pipe() - setImmediate(function() { - if (!destroyed) inflateFilter.emit("error", err); - }); - }); - readStream.pipe(inflateFilter); - - if (self.validateEntrySizes) { - endpointStream = new AssertByteCountStream(uncompressedSize); - inflateFilter.on("error", function(err) { - // forward zlib errors to the client-visible stream - setImmediate(function() { - if (!destroyed) endpointStream.emit("error", err); - }); - }); - inflateFilter.pipe(endpointStream); - } else { - // the zlib filter is the client-visible stream - endpointStream = inflateFilter; - } - // this is part of yauzl's API, so implement this function on the client-visible stream - installDestroyFn(endpointStream, function() { - destroyed = true; - if (inflateFilter !== endpointStream) inflateFilter.unpipe(endpointStream); - readStream.unpipe(inflateFilter); - // TODO: the inflateFilter may cause a memory leak. see Issue #27. - readStream.destroy(); - }); - } - callback(null, endpointStream); -}; - -ZipFile.prototype.readLocalFileHeader = function(entry, options, callback) { - var self = this; - if (callback == null) { - callback = options; - options = null; - } - if (options == null) options = {}; - - self.reader.ref(); - var buffer = newBuffer(30); - readAndAssertNoEof(self.reader, buffer, 0, buffer.length, entry.relativeOffsetOfLocalHeader, function(err) { - try { - if (err) return callback(err); - // 0 - Local file header signature = 0x04034b50 - var signature = buffer.readUInt32LE(0); - if (signature !== 0x04034b50) { - return callback(new Error("invalid local file header signature: 0x" + signature.toString(16))); - } - - var fileNameLength = buffer.readUInt16LE(26); - var extraFieldLength = buffer.readUInt16LE(28); - var fileDataStart = entry.relativeOffsetOfLocalHeader + 30 + fileNameLength + extraFieldLength; - // We now have enough information to do this bounds check. - if (fileDataStart + entry.compressedSize > self.fileSize) { - return callback(new Error("file data overflows file bounds: " + - fileDataStart + " + " + entry.compressedSize + " > " + self.fileSize)); - } - - if (options.minimal) { - return callback(null, {fileDataStart: fileDataStart}); - } - - var localFileHeader = new LocalFileHeader(); - localFileHeader.fileDataStart = fileDataStart; - - // 4 - Version needed to extract (minimum) - localFileHeader.versionNeededToExtract = buffer.readUInt16LE(4); - // 6 - General purpose bit flag - localFileHeader.generalPurposeBitFlag = buffer.readUInt16LE(6); - // 8 - Compression method - localFileHeader.compressionMethod = buffer.readUInt16LE(8); - // 10 - File last modification time - localFileHeader.lastModFileTime = buffer.readUInt16LE(10); - // 12 - File last modification date - localFileHeader.lastModFileDate = buffer.readUInt16LE(12); - // 14 - CRC-32 - localFileHeader.crc32 = buffer.readUInt32LE(14); - // 18 - Compressed size - localFileHeader.compressedSize = buffer.readUInt32LE(18); - // 22 - Uncompressed size - localFileHeader.uncompressedSize = buffer.readUInt32LE(22); - // 26 - File name length (n) - localFileHeader.fileNameLength = fileNameLength; - // 28 - Extra field length (m) - localFileHeader.extraFieldLength = extraFieldLength; - // 30 - File name - // 30+n - Extra field - - buffer = newBuffer(fileNameLength + extraFieldLength); - self.reader.ref(); - readAndAssertNoEof(self.reader, buffer, 0, buffer.length, entry.relativeOffsetOfLocalHeader + 30, function(err) { - try { - if (err) return callback(err); - localFileHeader.fileName = buffer.subarray(0, fileNameLength); - localFileHeader.extraField = buffer.subarray(fileNameLength); - return callback(null, localFileHeader); - } finally { - self.reader.unref(); - } - }); - } finally { - self.reader.unref(); - } - }); -}; - -function Entry() { -} -Entry.prototype.getLastModDate = function(options) { - if (options == null) options = {}; - - if (!options.forceDosFormat) { - // Check extended fields. - for (var i = 0; i < this.extraFields.length; i++) { - var extraField = this.extraFields[i]; - if (extraField.id === 0x5455) { - // Info-ZIP "universal timestamp" extended field (`0x5455` aka `"UT"`). - // See the Info-ZIP source code unix/unix.c:set_extra_field() and zipfile.c:ef_scan_ut_time(). - var data = extraField.data; - if (data.length < 5) continue; // Too short. - // The flags define which of the three fields are present: mtime, atime, ctime. - // We only care about mtime. - // Also, ctime is never included in practice. - // And also, atime is only included in the local file header for some reason - // despite the flags lying about its inclusion in the central header. - var flags = data[0]; - var HAS_MTIME = 1; - if (!(flags & HAS_MTIME)) continue; // This will realistically never happen. - // Although the positions of all of the fields shift around depending on the presence of other fields, - // mtime is always first if present, and that's the only one we care about. - var posixTimestamp = data.readInt32LE(1); - return new Date(posixTimestamp * 1000); - } else if (extraField.id === 0x000a) { - var data = extraField.data; - if (data.length !== 32) continue; // The length is always the same. - // 4 bytes reserved - // 2 bytes Tag - if (data.readUInt16LE(4) !== 1) continue; // Tag1 is actually the only defined Tag. - // 2 bytes Size - if (data.readUInt16LE(6) !== 24) continue; // Size is always 24. - // 8 bytes Mtime - var hundredNanoSecondsSince1601 = data.readUInt32LE(8) + 4294967296 * data.readInt32LE(12); - // Convert from NTFS to POSIX milliseconds. - // The big number below is the milliseconds between year 1601 and year 1970 - // (i.e. the negative POSIX timestamp of 1601-01-01 00:00:00Z) - var millisecondsSince1970 = hundredNanoSecondsSince1601 / 10000 - 11644473600000; - // Note on numeric precision: JavaScript Number objects lose precision above Number.MAX_SAFE_INTEGER, - // and NTFS timestamps are typically much bigger than that limit. - // (MAX_SAFE_INTEGER would represent 1629-07-17T23:58:45.475Z.) - // However, we're losing precision in the conversion from 100nanosecond units to millisecond units anyway, - // and the time at which we also lose 1-millisecond precision is year 275760, the JavaScript Date limit (by design). - // Up through the year 2057, this conversion only drops 4 bits of precision, - // which is well under the 13-14 bits ratio between the milliseconds and 100nanoseconds. - return new Date(millisecondsSince1970); - } - } - } - - // Fallback to non-extended encoding. - return dosDateTimeToDate(this.lastModFileDate, this.lastModFileTime, options.timezone); -}; -Entry.prototype.isEncrypted = function() { - return (this.generalPurposeBitFlag & 0x1) !== 0; -}; -Entry.prototype.isCompressed = function() { - return this.compressionMethod === 8; -}; - -function LocalFileHeader() { -} - -function dosDateTimeToDate(date, time, timezone) { - var day = date & 0x1f; // 1-31 - var month = (date >> 5 & 0xf) - 1; // 1-12, 0-11 - var year = (date >> 9 & 0x7f) + 1980; // 0-128, 1980-2108 - - var millisecond = 0; - var second = (time & 0x1f) * 2; // 0-29, 0-58 (even numbers) - var minute = time >> 5 & 0x3f; // 0-59 - var hour = time >> 11 & 0x1f; // 0-23 - - if (timezone == null || timezone === "local") { - return new Date(year, month, day, hour, minute, second, millisecond); - } else if (timezone === "UTC") { - return new Date(Date.UTC(year, month, day, hour, minute, second, millisecond)); - } else { - throw new Error("unrecognized options.timezone: " + options.timezone); - } -} - -function getFileNameLowLevel(generalPurposeBitFlag, fileNameBuffer, extraFields, strictFileNames) { - var fileName = null; - - // check for Info-ZIP Unicode Path Extra Field (0x7075) - // see https://github.com/thejoshwolfe/yauzl/issues/33 - for (var i = 0; i < extraFields.length; i++) { - var extraField = extraFields[i]; - if (extraField.id === 0x7075) { - if (extraField.data.length < 6) { - // too short to be meaningful - continue; - } - // Version 1 byte version of this extra field, currently 1 - if (extraField.data.readUInt8(0) !== 1) { - // > Changes may not be backward compatible so this extra - // > field should not be used if the version is not recognized. - continue; - } - // NameCRC32 4 bytes File Name Field CRC32 Checksum - var oldNameCrc32 = extraField.data.readUInt32LE(1); - if (crc32.unsigned(fileNameBuffer) !== oldNameCrc32) { - // > If the CRC check fails, this UTF-8 Path Extra Field should be - // > ignored and the File Name field in the header should be used instead. - continue; - } - // UnicodeName Variable UTF-8 version of the entry File Name - fileName = decodeBuffer(extraField.data.subarray(5), true); - break; - } - } - - if (fileName == null) { - // The typical case. - var isUtf8 = (generalPurposeBitFlag & 0x800) !== 0; - fileName = decodeBuffer(fileNameBuffer, isUtf8); - } - - if (!strictFileNames) { - // Allow backslash. - fileName = fileName.replace(/\\/g, "/"); - } - return fileName; -} - -function validateFileName(fileName) { - if (fileName.indexOf("\\") !== -1) { - return "invalid characters in fileName: " + fileName; - } - if (/^[a-zA-Z]:/.test(fileName) || /^\//.test(fileName)) { - return "absolute path: " + fileName; - } - if (fileName.split("/").indexOf("..") !== -1) { - return "invalid relative path: " + fileName; - } - // all good - return null; -} - -function parseExtraFields(extraFieldBuffer) { - var extraFields = []; - var i = 0; - while (i < extraFieldBuffer.length - 3) { - var headerId = extraFieldBuffer.readUInt16LE(i + 0); - var dataSize = extraFieldBuffer.readUInt16LE(i + 2); - var dataStart = i + 4; - var dataEnd = dataStart + dataSize; - if (dataEnd > extraFieldBuffer.length) throw new Error("extra field length exceeds extra field buffer size"); - var dataBuffer = extraFieldBuffer.subarray(dataStart, dataEnd); - extraFields.push({ - id: headerId, - data: dataBuffer, - }); - i = dataEnd; - } - return extraFields; -} - -function readAndAssertNoEof(reader, buffer, offset, length, position, callback) { - if (length === 0) { - // fs.read will throw an out-of-bounds error if you try to read 0 bytes from a 0 byte file - return setImmediate(function() { callback(null, newBuffer(0)); }); - } - reader.read(buffer, offset, length, position, function(err, bytesRead) { - if (err) return callback(err); - if (bytesRead < length) { - return callback(new Error("unexpected EOF")); - } - callback(); - }); -} - -util.inherits(AssertByteCountStream, Transform); -function AssertByteCountStream(byteCount) { - Transform.call(this); - this.actualByteCount = 0; - this.expectedByteCount = byteCount; -} -AssertByteCountStream.prototype._transform = function(chunk, encoding, cb) { - this.actualByteCount += chunk.length; - if (this.actualByteCount > this.expectedByteCount) { - var msg = "too many bytes in the stream. expected " + this.expectedByteCount + ". got at least " + this.actualByteCount; - return cb(new Error(msg)); - } - cb(null, chunk); -}; -AssertByteCountStream.prototype._flush = function(cb) { - if (this.actualByteCount < this.expectedByteCount) { - var msg = "not enough bytes in the stream. expected " + this.expectedByteCount + ". got only " + this.actualByteCount; - return cb(new Error(msg)); - } - cb(); -}; - -util.inherits(RandomAccessReader, EventEmitter); -function RandomAccessReader() { - EventEmitter.call(this); - this.refCount = 0; -} -RandomAccessReader.prototype.ref = function() { - this.refCount += 1; -}; -RandomAccessReader.prototype.unref = function() { - var self = this; - self.refCount -= 1; - - if (self.refCount > 0) return; - if (self.refCount < 0) throw new Error("invalid unref"); - - self.close(onCloseDone); - - function onCloseDone(err) { - if (err) return self.emit('error', err); - self.emit('close'); - } -}; -RandomAccessReader.prototype.createReadStream = function(options) { - if (options == null) options = {}; - var start = options.start; - var end = options.end; - if (start === end) { - var emptyStream = new PassThrough(); - setImmediate(function() { - emptyStream.end(); - }); - return emptyStream; - } - var stream = this._readStreamForRange(start, end); - - var destroyed = false; - var refUnrefFilter = new RefUnrefFilter(this); - stream.on("error", function(err) { - setImmediate(function() { - if (!destroyed) refUnrefFilter.emit("error", err); - }); - }); - installDestroyFn(refUnrefFilter, function() { - stream.unpipe(refUnrefFilter); - refUnrefFilter.unref(); - stream.destroy(); - }); - - var byteCounter = new AssertByteCountStream(end - start); - refUnrefFilter.on("error", function(err) { - setImmediate(function() { - if (!destroyed) byteCounter.emit("error", err); - }); - }); - installDestroyFn(byteCounter, function() { - destroyed = true; - refUnrefFilter.unpipe(byteCounter); - refUnrefFilter.destroy(); - }); - - return stream.pipe(refUnrefFilter).pipe(byteCounter); -}; -RandomAccessReader.prototype._readStreamForRange = function(start, end) { - throw new Error("not implemented"); -}; -RandomAccessReader.prototype.read = function(buffer, offset, length, position, callback) { - var readStream = this.createReadStream({start: position, end: position + length}); - var writeStream = new Writable(); - var written = 0; - writeStream._write = function(chunk, encoding, cb) { - chunk.copy(buffer, offset + written, 0, chunk.length); - written += chunk.length; - cb(); - }; - writeStream.on("finish", callback); - readStream.on("error", function(error) { - callback(error); - }); - readStream.pipe(writeStream); -}; -RandomAccessReader.prototype.close = function(callback) { - setImmediate(callback); -}; - -util.inherits(RefUnrefFilter, PassThrough); -function RefUnrefFilter(context) { - PassThrough.call(this); - this.context = context; - this.context.ref(); - this.unreffedYet = false; -} -RefUnrefFilter.prototype._flush = function(cb) { - this.unref(); - cb(); -}; -RefUnrefFilter.prototype.unref = function(cb) { - if (this.unreffedYet) return; - this.unreffedYet = true; - this.context.unref(); -}; - -var cp437 = '\u0000☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ '; -function decodeBuffer(buffer, isUtf8) { - if (isUtf8) { - return buffer.toString("utf8"); - } else { - var result = ""; - for (var i = 0; i < buffer.length; i++) { - result += cp437[buffer[i]]; - } - return result; - } -} - -function readUInt64LE(buffer, offset) { - // There is no native function for this, because we can't actually store 64-bit integers precisely. - // after 53 bits, JavaScript's Number type (IEEE 754 double) can't store individual integers anymore. - // but since 53 bits is a whole lot more than 32 bits, we do our best anyway. - // As of 2020, Node has added support for BigInt, which obviates this whole function, - // but yauzl hasn't been updated to depend on BigInt (yet?). - var lower32 = buffer.readUInt32LE(offset); - var upper32 = buffer.readUInt32LE(offset + 4); - // we can't use bitshifting here, because JavaScript bitshifting only works on 32-bit integers. - return upper32 * 0x100000000 + lower32; - // as long as we're bounds checking the result of this function against the total file size, - // we'll catch any overflow errors, because we already made sure the total file size was within reason. -} - -// Node 10 deprecated new Buffer(). -var newBuffer; -if (typeof Buffer.allocUnsafe === "function") { - newBuffer = function(len) { - return Buffer.allocUnsafe(len); - }; -} else { - newBuffer = function(len) { - return new Buffer(len); - }; -} - -// Node 8 introduced a proper destroy() implementation on writable streams. -function installDestroyFn(stream, fn) { - if (typeof stream.destroy === "function") { - // New API. - stream._destroy = function(err, cb) { - fn(); - if (cb != null) cb(err); - }; - } else { - // Old API. - stream.destroy = fn; - } -} - -function defaultCallback(err) { - if (err) throw err; -} diff --git a/packages/utils/third_party/yauzl/pend.js b/packages/utils/third_party/yauzl/pend.js deleted file mode 100644 index c278333e8e8eb..0000000000000 --- a/packages/utils/third_party/yauzl/pend.js +++ /dev/null @@ -1,58 +0,0 @@ -// Vendored from https://github.com/andrewrk/node-pend at v1.2.0 under the MIT License. -// See LICENSE for the full text. - -module.exports = Pend; - -function Pend() { - this.pending = 0; - this.max = Infinity; - this.listeners = []; - this.waiting = []; - this.error = null; -} - -Pend.prototype.go = function(fn) { - if (this.pending < this.max) { - pendGo(this, fn); - } else { - this.waiting.push(fn); - } -}; - -Pend.prototype.wait = function(cb) { - if (this.pending === 0) { - cb(this.error); - } else { - this.listeners.push(cb); - } -}; - -Pend.prototype.hold = function() { - return pendHold(this); -}; - -function pendHold(self) { - self.pending += 1; - var called = false; - return onCb; - function onCb(err) { - if (called) throw new Error("callback called twice"); - called = true; - self.error = self.error || err; - self.pending -= 1; - if (self.waiting.length > 0 && self.pending < self.max) { - pendGo(self, self.waiting.shift()); - } else if (self.pending === 0) { - var listeners = self.listeners; - self.listeners = []; - listeners.forEach(cbListener); - } - } - function cbListener(listener) { - listener(self.error); - } -} - -function pendGo(self, fn) { - fn(pendHold(self)); -} diff --git a/packages/utils/zipFile.ts b/packages/utils/zipFile.ts index 4b8209bab00b9..9c59e5ae2443d 100644 --- a/packages/utils/zipFile.ts +++ b/packages/utils/zipFile.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import * as yauzl from './third_party/yauzl'; -import type { Entry, ZipFile as UnzipFile } from './third_party/yauzl'; +import * as yauzl from 'yauzl'; +import type { Entry, ZipFile as UnzipFile } from 'yauzl'; export class ZipFile { private _fileName: string; diff --git a/tests/installation/playwright-cli-install-should-work.spec.ts b/tests/installation/playwright-cli-install-should-work.spec.ts index ea8d9ecea1f0b..20bbfc1aa5c78 100755 --- a/tests/installation/playwright-cli-install-should-work.spec.ts +++ b/tests/installation/playwright-cli-install-should-work.spec.ts @@ -65,6 +65,24 @@ test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk } }); +test('install command should suppress progress bar with --no-progress', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41099' } }, async ({ exec, checkInstalledSoftwareOnDisk }) => { + await exec('npm i playwright'); + const result = await exec('npx playwright install chromium --no-progress'); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); + expect(result).not.toContain('% of'); + expect(result).not.toContain('■'); +}); + +test('install command should suppress progress bar with PLAYWRIGHT_DOWNLOAD_NO_PROGRESS', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41099' } }, async ({ exec, checkInstalledSoftwareOnDisk }) => { + await exec('npm i playwright'); + const result = await exec('npx playwright install chromium', { env: { PLAYWRIGHT_DOWNLOAD_NO_PROGRESS: '1' } }); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); + expect(result).not.toContain('% of'); + expect(result).not.toContain('■'); +}); + test('install command should work with HTTPS_PROXY', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36650' } }, async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); const proxy = await TestProxy.create(8947 + test.info().workerIndex * 4); diff --git a/tests/library/har-websocket.spec.ts b/tests/library/har-websocket.spec.ts index 0bea2c53c53e5..9b6edb24b9289 100644 --- a/tests/library/har-websocket.spec.ts +++ b/tests/library/har-websocket.spec.ts @@ -124,8 +124,8 @@ it('should include websocket messages', async ({ contextFactory, server }, testI { type: 'receive', opcode: 1, data: 'incoming' }, ]); for (const m of messages) { - expect(m.time).toBeGreaterThanOrEqual(beforeMs / 1000 - 1); - expect(m.time).toBeLessThanOrEqual(afterMs / 1000 + 1); + expect(m.time).toBeGreaterThanOrEqual(beforeMs - 1); + expect(m.time).toBeLessThanOrEqual(afterMs + 1); } expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); }); diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index dd88e25086800..467921af6aae1 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -106,6 +106,39 @@ it('should include request', async ({ contextFactory, server }, testInfo) => { expect(entry.request.bodySize).toBe(0); }); +it('should populate entry startedDateTime from the browser', async ({ contextFactory, server }, testInfo) => { + const { page, getLog } = await pageWithHar(contextFactory, testInfo); + await page.goto(server.EMPTY_PAGE); + + // The browser issues a request after a short delay, then we deliberately + // block Node's event loop. The protocol event for the request therefore + // queues up while Node is busy and is only processed by the harTracer after + // the block ends. If `startedDateTime` is populated from Node's clock at + // observation time it will land inside the busy-loop window (i.e. close to + // `unblockedAt`); if it comes from the browser via the debugging protocol + // it will be tied to when the browser actually sent the request. + await page.evaluate(() => { + setTimeout(() => { void fetch('/delayed-fetch'); }, 50); + }); + + const blockUntil = Date.now() + 300; + while (Date.now() < blockUntil) { + // Busy loop to prevent Node from processing protocol events. + } + const unblockedAt = Date.now(); + + await page.waitForResponse('**/delayed-fetch'); + const log = await getLog(); + + const entry = log.entries.find(e => e.request.url.endsWith('/delayed-fetch'))!; + const startedAt = new Date(entry.startedDateTime).valueOf(); + expect(Number.isFinite(startedAt)).toBe(true); + // The recorded time should be tied to when the browser actually sent the + // request (during the busy loop), not to when Node observed the protocol + // event (after the busy loop). + expect(startedAt).toBeLessThan(unblockedAt - 100); +}); + it('should include response', async ({ contextFactory, server }, testInfo) => { const { page, getLog } = await pageWithHar(contextFactory, testInfo); await page.goto(server.EMPTY_PAGE); diff --git a/tests/library/heap.spec.ts b/tests/library/heap.spec.ts index ea8f994e8058c..2df46a8112b0b 100644 --- a/tests/library/heap.spec.ts +++ b/tests/library/heap.spec.ts @@ -165,7 +165,46 @@ test.describe(() => { } }); + test('should collect frames', async ({ page, server }) => { + test.slow(); + + const kFrameCount = 310; + + await page.goto(server.EMPTY_PAGE); + let cb; + const promise = new Promise(f => cb = f); + let counter = 0; + page.on('frameattached', async () => { + // Make sure we can access page. + await page.title(); + if (++counter === kFrameCount) + cb(); + }); + + page.evaluate(async ({ url, count }) => { + for (let i = 0; i < count; i++) { + const frame = document.createElement('iframe'); + frame.src = url; + document.body.appendChild(frame); + await new Promise(f => setTimeout(f, 10)); + frame.remove(); + } + }, { url: server.PREFIX + '/one-style.html', count: kFrameCount }).catch(() => {}); + await promise; + await page.waitForTimeout(500); + }); + test.afterEach(() => { coreServer.setMaxDispatchersForTest(null); }); }); + +test('cycle handles', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(`
hi
`.repeat(2000)); + const divs = await page.$$('div'); + for (const div of divs) { + const span = await div.$('span'); + expect(await span.textContent()).toBe('hi'); + } +}); diff --git a/tests/page/page-set-input-files.spec.ts b/tests/page/page-set-input-files.spec.ts index c443700b81a20..6a0922a330211 100644 --- a/tests/page/page-set-input-files.spec.ts +++ b/tests/page/page-set-input-files.spec.ts @@ -145,7 +145,7 @@ test('should upload a file after popup', async ({ page, server, asset }) => { test('should upload large file', async ({ page, server, isAndroid, mode }, testInfo) => { test.skip(isAndroid); - test.skip(mode.startsWith('service')); + test.skip(mode !== 'default'); test.slow(); await page.goto(server.PREFIX + '/input/fileupload.html'); diff --git a/tests/playwright-test/match-grep.spec.ts b/tests/playwright-test/match-grep.spec.ts index 357c897bcd7be..a79825c3d0d6f 100644 --- a/tests/playwright-test/match-grep.spec.ts +++ b/tests/playwright-test/match-grep.spec.ts @@ -88,6 +88,13 @@ test('should grep invert test name', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); }); +test('should grep invert test name with short option', async ({ runInlineTest }) => { + const result = await runInlineTest(files, undefined, undefined, { additionalArgs: ['-G', 'BB'] }); + expect(result.passed).toBe(6); + expect(result.skipped).toBe(0); + expect(result.exitCode).toBe(0); +}); + test('should be case insensitive by default', async ({ runInlineTest }) => { const result = await runInlineTest(files, { 'grep': 'TesT Cc' }); expect(result.passed).toBe(3); diff --git a/tests/playwright-test/stable-test-runner/package-lock.json b/tests/playwright-test/stable-test-runner/package-lock.json index d6d23a2742a99..7ebb02a95b13f 100644 --- a/tests/playwright-test/stable-test-runner/package-lock.json +++ b/tests/playwright-test/stable-test-runner/package-lock.json @@ -5,16 +5,16 @@ "packages": { "": { "dependencies": { - "@playwright/test": "^1.61.0-alpha-2026-05-27" + "@playwright/test": "^1.61.0-alpha-2026-06-01" } }, "node_modules/@playwright/test": { - "version": "1.61.0-alpha-2026-05-27", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.0-alpha-2026-05-27.tgz", - "integrity": "sha512-+dibLBwssFsuRSxp8ncHFzMd4YI+lRliPGejlai5ArU8f6bkZtP0xlnNMji15b9KulsNKCb74w4kBE3C1x22TA==", + "version": "1.61.0-alpha-2026-06-01", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.0-alpha-2026-06-01.tgz", + "integrity": "sha512-eE/Hz7GLQeymq0axISbYtgpeBIltzcAbWFY8algIzSwPsyvTFt8mCh/mKJJl8zUjOFz2EtL8OVedDL8stKNS/Q==", "license": "Apache-2.0", "dependencies": { - "playwright": "1.61.0-alpha-2026-05-27" + "playwright": "1.61.0-alpha-2026-06-01" }, "bin": { "playwright": "cli.js" @@ -38,12 +38,12 @@ } }, "node_modules/playwright": { - "version": "1.61.0-alpha-2026-05-27", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0-alpha-2026-05-27.tgz", - "integrity": "sha512-PYUmcIBYj/8nAQtbCzt1dlNBIhVlS6MCbdxKPScbZWDw4GsmgEdt9aDxfhHLX4Ko+atKiEjzktzhToHWgVzS4g==", + "version": "1.61.0-alpha-2026-06-01", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0-alpha-2026-06-01.tgz", + "integrity": "sha512-tBlatMRW9V7IZl50faQpIkHs7qz7U5oxw2raeDRlc1uMd91hVpsE6XIp2fY1Z6hVIYkQeCeO3I5NP9J5vKbSvQ==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.61.0-alpha-2026-05-27" + "playwright-core": "1.61.0-alpha-2026-06-01" }, "bin": { "playwright": "cli.js" @@ -56,9 +56,9 @@ } }, "node_modules/playwright-core": { - "version": "1.61.0-alpha-2026-05-27", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0-alpha-2026-05-27.tgz", - "integrity": "sha512-Ec+vs3IKh3RXYLp0H7+wfuovuEBKNK/dyfO9GwsX9AzlXwTSW5kBoXL6q2QrBrbEkde3nNcWYZzIQ1qK6SJiHw==", + "version": "1.61.0-alpha-2026-06-01", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0-alpha-2026-06-01.tgz", + "integrity": "sha512-QP5JCCuFJ+Yoh3dbXqSHVqV76VblK1T+dFIDs9RI9iRCEU/hlOpvt9cKJJXivVo7i+9XJgYfs2ERzvCaNZ0Y/g==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" diff --git a/tests/playwright-test/stable-test-runner/package.json b/tests/playwright-test/stable-test-runner/package.json index 199d10eb0e28a..307d2f5489376 100644 --- a/tests/playwright-test/stable-test-runner/package.json +++ b/tests/playwright-test/stable-test-runner/package.json @@ -1,6 +1,6 @@ { "private": true, "dependencies": { - "@playwright/test": "^1.61.0-alpha-2026-05-27" + "@playwright/test": "^1.61.0-alpha-2026-06-01" } } diff --git a/tests/stress/frames.spec.ts b/tests/stress/frames.spec.ts deleted file mode 100644 index a2002f41a733a..0000000000000 --- a/tests/stress/frames.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 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. - */ - -import { contextTest as test, expect } from '../config/browserTest'; -import { server as coreServer } from '../../packages/playwright-core/lib/coreBundle'; - -test.slow(); - -test('cycle frames', async ({ page, server }) => { - coreServer.setMaxDispatchersForTest(100); - - const kFrameCount = 310; - - await page.goto(server.EMPTY_PAGE); - let cb; - const promise = new Promise(f => cb = f); - let counter = 0; - page.on('frameattached', async () => { - // Make sure we can access page. - await page.title(); - if (++counter === kFrameCount) - cb(); - }); - - page.evaluate(async ({ url, count }) => { - for (let i = 0; i < count; i++) { - const frame = document.createElement('iframe'); - frame.src = url; - document.body.appendChild(frame); - await new Promise(f => setTimeout(f, 10)); - frame.remove(); - } - }, { url: server.PREFIX + '/one-style.html', count: kFrameCount }).catch(() => {}); - await promise; - await page.waitForTimeout(500); - - coreServer.setMaxDispatchersForTest(null); -}); - -test('cycle handles', async ({ page, server }) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent(`
hi
`.repeat(2000)); - const divs = await page.$$('div'); - for (const div of divs) { - const span = await div.$('span'); - expect(await span.textContent()).toBe('hi'); - } -}); diff --git a/tests/webview/expectations/webkit-webview-page.txt b/tests/webview/expectations/webkit-webview-page.txt index 352488eb3e263..20010dc7b58b9 100644 --- a/tests/webview/expectations/webkit-webview-page.txt +++ b/tests/webview/expectations/webkit-webview-page.txt @@ -124,13 +124,15 @@ page/elementhandle-screenshot.spec.ts › element screenshot › should not issu page/locator-misc-1.spec.ts › should focus and blur a button [fail] # ============================================================================ -# service-worker (2 tests) +# service-worker (3 tests) # Service-worker request interception/reporting is unsupported on the stock # WebView backend; routing a request through a service worker tears down the -# session ('Target closed'). +# session ('Target closed'). Passes in isolation but fails in the full run +# because service-worker registration persists across tests. # ============================================================================ page/interception.spec.ts › should intercept after a service worker [fail] page/page-event-request.spec.ts › should report requests and responses handled by service worker with routing [fail] +page/page-goto.spec.ts › should be able to navigate to a page controlled by service worker [fail] # ============================================================================ # out-of-process-iframe (1 test) @@ -305,7 +307,6 @@ page/page-goto.spec.ts › should fail when navigating to bad url [fail] page/page-goto.spec.ts › should fail when replaced by another navigation [fail] page/page-goto.spec.ts › should override referrer-policy [fail] page/page-goto.spec.ts › should return url with basic auth info [fail] -page/page-goto.spec.ts › should return when navigation is committed if commit is specified [fail] page/page-goto.spec.ts › should send referer [fail] page/page-goto.spec.ts › should send referer of cross-origin URL [fail] page/page-goto.spec.ts › should work with cross-process that fails before committing [fail] @@ -373,7 +374,7 @@ page/page-wait-for-url.spec.ts › should work with DOM history.back()/history.f page/wheel.spec.ts › should work when the event is canceled [fail] # ============================================================================ -# misc (98 tests) +# misc (99 tests) # Failures that have not been grouped yet — eyeball before extending the # categories above. # ============================================================================ @@ -419,13 +420,11 @@ page/page-focus.spec.ts › clicking checkbox should activate it [fail] page/page-focus.spec.ts › tab should cycle between document elements and browser [fail] page/page-focus.spec.ts › tab should cycle between single input and browser [fail] page/page-goto.spec.ts › js redirect overrides url bar navigation [fail] -page/page-goto.spec.ts › should be able to navigate to a page controlled by service worker [fail] page/page-goto.spec.ts › should fail when main resources failed to load [fail] page/page-goto.spec.ts › should fail when navigating and show the url at the error message [fail] page/page-goto.spec.ts › should fail when navigating to bad SSL [fail] page/page-goto.spec.ts › should fail when navigating to bad SSL after redirects [fail] -page/page-goto.spec.ts › should not crash when navigating to bad SSL after a cross origin navigation [fail] -page/page-goto.spec.ts › should not throw if networkidle0 is passed as an option [fail] +page/page-goto.spec.ts › should fail when server returns 204 [fail] page/page-goto.spec.ts › should work cross-process [fail] page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy [fail] page/page-goto.spec.ts › should work with Cross-Origin-Opener-Policy after redirect [fail] @@ -532,7 +531,7 @@ page/page-mouse.spec.ts › should trigger hover state on disabled button [fail] page/page-mouse.spec.ts › should trigger hover state with removed window.Node [fail] # ============================================================================ -# timeout (28 tests) +# timeout (26 tests) # Test timed out — usually because an unimplemented hook never resolves. # Re-triage once the relevant delegate methods are wired up. # ============================================================================ @@ -552,7 +551,6 @@ page/page-click.spec.ts › should wait for becoming hit target [fail] page/page-drag.spec.ts › Drag and drop › should respect the drop effect [fail] page/page-drag.spec.ts › Drag and drop › should work if a frame is stalled [fail] page/page-evaluate.spec.ts › should return -Infinity [fail] -page/page-goto.spec.ts › should fail when exceeding default maximum navigation timeout [fail] page/page-goto.spec.ts › should not leak listeners during bad navigation [fail] page/page-goto.spec.ts › should not throw unhandled rejections on invalid url [fail] page/page-history.spec.ts › page.goBack during renderer-initiated navigation [fail] @@ -563,7 +561,6 @@ page/page-request-continue.spec.ts › should not throw if request was cancelled page/page-route.spec.ts › should not throw "Invalid Interception Id" if the request was cancelled [fail] page/page-screenshot.spec.ts › should capture css box-shadow [fail] page/to-match-aria-snapshot.spec.ts › should not match what is not matched [fail] -page/workers.spec.ts › should have JSHandles for console logs [fail] # ============================================================================ # structural-unsupported (111 tests) @@ -654,7 +651,6 @@ page/page-filechooser.spec.ts › should work when file input is not attached to page/page-filechooser.spec.ts › should work with no timeout [fail] page/page-goto.spec.ts › should capture cross-process iframe navigation request [fail] page/page-goto.spec.ts › should capture iframe navigation request [fail] -page/page-goto.spec.ts › should wait for load when iframe attaches and detaches [fail] page/page-goto.spec.ts › should work with lazy loading iframes [fail] page/page-network-idle.spec.ts › should wait for networkidle from the popup [fail] page/page-network-idle.spec.ts › should wait for networkidle when iframe attaches and detaches [fail] @@ -708,10 +704,11 @@ page/page-fill.spec.ts › should throw on incorrect color value [fail] page/page-fill.spec.ts › should throw on incorrect datetime-local [fail] # ============================================================================ -# target-closed-cascade (120 tests) -# Cascade failures: the worker session died (likely from one of the unimplemented -# methods above) and every following test sees 'Target closed'. Once the root -# causes above are addressed, re-run and prune this section. +# target-closed-cascade (107 tests) +# Cascade failures: a test tears down the page session and every following test +# sees 'Target closed'. A confirmed trigger is cross-process navigation, which +# leaves the page target dead for the next test's first navigation (see the +# worker-remaining section). Re-run and prune this section as triggers are fixed. # ============================================================================ page/elementhandle-click.spec.ts › should work @smoke [fail] page/elementhandle-owner-frame.spec.ts › should work for adopted elements [fail] @@ -772,6 +769,12 @@ page/page-expose-function.spec.ts › should work on frames before navigation [f page/page-expose-function.spec.ts › should work with complex objects [fail] page/page-expose-function.spec.ts › should work with setContent [fail] page/page-goto.spec.ts › js redirect overrides url bar navigation [fail] +# First does a cross-origin navigation that kills the page target; second is a +# plain navigation that inherits 'Target closed' when it runs after such a +# trigger. Keep skipped until cross-process navigation stops tearing down the +# session. +page/page-goto.spec.ts › should not crash when navigating to bad SSL after a cross origin navigation [fail] +page/page-goto.spec.ts › should not throw if networkidle0 is passed as an option [fail] page/page-request-fulfill.spec.ts › should fulfill preload link requests [fail] page/page-request-intercept.spec.ts › should give access to the intercepted response [fail] page/page-request-intercept.spec.ts › should intercept with post data override [fail] @@ -818,18 +821,16 @@ page/selectors-frame.spec.ts › should work for $eval [fail] page/selectors-frame.spec.ts › should work for $eval (handle) [fail] page/selectors-frame.spec.ts › waitFor should survive frame reattach [fail] page/selectors-frame.spec.ts › waitForSelector should survive frame reattach (handle) [fail] -page/workers.spec.ts › Page.workers @smoke [fail] +# ============================================================================ +# worker-remaining (2 tests) +# Web Workers attach over the WebView backend now (Worker.enable is sent during +# session init), so page/workers.spec.ts mostly passes. Two gaps remain: +# - 'should clear upon cross-process navigation' passes its own assertions but +# the cross-process target swap leaves the page in a 'Target closed' state for +# the next test (same root cause as target-closed-cascade above). +# - 'should support extra http headers': setExtraHTTPHeaders is not applied to +# the worker script request, so worker.js arrives without the header. +# ============================================================================ page/workers.spec.ts › should clear upon cross-process navigation [fail] -page/workers.spec.ts › should clear upon navigation [fail] -page/workers.spec.ts › should dispatch console messages when page has workers [fail] -page/workers.spec.ts › should emit created and destroyed events [fail] -page/workers.spec.ts › should evaluate [fail] -page/workers.spec.ts › should have timestamp on worker console messages [fail] -page/workers.spec.ts › should not report console logs from workers twice [fail] -page/workers.spec.ts › should report console event on the worker [fail] -page/workers.spec.ts › should report console event on the worker when not listening on page or context [fail] -page/workers.spec.ts › should report console logs [fail] -page/workers.spec.ts › should report network activity [fail] -page/workers.spec.ts › should report worker script as network request after redirect [fail] page/workers.spec.ts › should support extra http headers [fail] diff --git a/utils/build/utilsBundleMapping.js b/utils/build/utilsBundleMapping.js index 1ee21a049b14f..3020a6ea86c3f 100644 --- a/utils/build/utilsBundleMapping.js +++ b/utils/build/utilsBundleMapping.js @@ -33,6 +33,7 @@ const MAPPING = { 'chokidar': { default: 'chokidar' }, 'get-east-asian-width': { namespace: 'getEastAsianWidth' }, 'yazl': { namespace: 'yazl' }, + 'yauzl': { default: 'yauzl', namespace: 'yauzl' }, 'zod': { namespace: 'z' }, 'zod-to-json-schema': { named: { zodToJsonSchema: 'zodToJsonSchema' } }, '@modelcontextprotocol/sdk/client/index.js': { named: { Client: 'Client' } }, diff --git a/utils/protocol-types-generator/index.js b/utils/protocol-types-generator/index.js index 6f8a1ebeb562b..d3eb3a8ed941f 100644 --- a/utils/protocol-types-generator/index.js +++ b/utils/protocol-types-generator/index.js @@ -1,7 +1,7 @@ // @ts-check const path = require('path'); const fs = require('fs'); -const yauzl = require('../../packages/utils/third_party/yauzl'); +const yauzl = require('yauzl'); const vm = require('vm'); const os = require('os');