Skip to content

Commit f97bad0

Browse files
committed
chore: download browser downloads in-memory
1 parent 64c553b commit f97bad0

File tree

4 files changed

+26
-37
lines changed

4 files changed

+26
-37
lines changed

packages/playwright-core/bundles/zip/src/third_party/extract-zip.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ declare namespace extract {
4949
}
5050

5151
declare function extract(
52-
zipPath: string,
52+
zipPath: Buffer,
5353
opts: extract.Options,
5454
): Promise<void>;
5555

packages/playwright-core/bundles/zip/src/third_party/extract-zip.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,21 @@ const { promisify } = require('util')
3333
const stream = require('stream')
3434
const yauzl = require('yauzl')
3535

36-
const openZip = promisify(yauzl.open)
36+
const openZipFromBuffer = promisify(yauzl.fromBuffer)
3737
const pipeline = promisify(stream.pipeline)
3838

3939
class Extractor {
40-
constructor (zipPath, opts) {
41-
this.zipPath = zipPath
40+
constructor (zipFile, opts) {
41+
/** @type {Buffer} */
42+
this.zipFile = zipFile
4243
this.opts = opts
4344
}
4445

4546
async extract () {
46-
debug('opening', this.zipPath, 'with opts', this.opts)
47+
debug('opening', `${this.zipFile.byteLength} bytes`, 'with opts', this.opts);
4748

48-
this.zipfile = await openZip(this.zipPath, { lazyEntries: true })
49-
this.canceled = false
49+
this.zipfile = await openZipFromBuffer(this.zipFile, { lazyEntries: true });
50+
this.canceled = false;
5051

5152
return new Promise((resolve, reject) => {
5253
this.zipfile.on('error', err => {
@@ -55,7 +56,7 @@ class Extractor {
5556
})
5657
this.zipfile.readEntry()
5758

58-
this.zipfile.on('close', () => {
59+
this.zipfile.on('end', () => {
5960
if (!this.canceled) {
6061
debug('zip extraction complete')
6162
resolve()
@@ -186,7 +187,7 @@ class Extractor {
186187
}
187188
}
188189

189-
module.exports = async function (zipPath, opts) {
190+
module.exports = async function (zipFile, opts) {
190191
debug('creating target directory', opts.dir)
191192

192193
if (!path.isAbsolute(opts.dir)) {
@@ -195,5 +196,5 @@ module.exports = async function (zipPath, opts) {
195196

196197
await fs.mkdir(opts.dir, { recursive: true })
197198
opts.dir = await fs.realpath(opts.dir)
198-
return new Extractor(zipPath, opts).extract()
199+
return new Extractor(zipFile, opts).extract()
199200
}

packages/playwright-core/src/server/registry/browserFetcher.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,17 @@ export async function downloadBrowserWithProgressBar(title: string, browserDirec
3737
return false;
3838
}
3939

40-
const zipPath = path.join(os.tmpdir(), downloadFileName);
4140
try {
4241
const retryCount = 5;
4342
for (let attempt = 1; attempt <= retryCount; ++attempt) {
4443
debugLogger.log('install', `downloading ${title} - attempt #${attempt}`);
4544
const url = downloadURLs[(attempt - 1) % downloadURLs.length];
4645
logPolitely(`Downloading ${title}` + colors.dim(` from ${url}`));
47-
const { error } = await downloadBrowserWithProgressBarOutOfProcess(title, browserDirectory, url, zipPath, executablePath, downloadSocketTimeout);
46+
const { error } = await downloadBrowserWithProgressBarOutOfProcess(title, browserDirectory, url, executablePath, downloadSocketTimeout);
4847
if (!error) {
4948
debugLogger.log('install', `SUCCESS installing ${title}`);
5049
break;
5150
}
52-
if (await existsAsync(zipPath))
53-
await fs.promises.unlink(zipPath);
5451
if (await existsAsync(browserDirectory))
5552
await fs.promises.rmdir(browserDirectory, { recursive: true });
5653
const errorMessage = error?.message || '';
@@ -62,9 +59,6 @@ export async function downloadBrowserWithProgressBar(title: string, browserDirec
6259
debugLogger.log('install', `FAILED installation ${title} with error: ${e}`);
6360
process.exitCode = 1;
6461
throw e;
65-
} finally {
66-
if (await existsAsync(zipPath))
67-
await fs.promises.unlink(zipPath);
6862
}
6963
logPolitely(`${title} downloaded to ${browserDirectory}`);
7064
return true;
@@ -75,7 +69,7 @@ export async function downloadBrowserWithProgressBar(title: string, browserDirec
7569
* Thats why we execute it in a separate process and check manually if the destination file exists.
7670
* https://github.com/microsoft/playwright/issues/17394
7771
*/
78-
function downloadBrowserWithProgressBarOutOfProcess(title: string, browserDirectory: string, url: string, zipPath: string, executablePath: string | undefined, socketTimeout: number): Promise<{ error: Error | null }> {
72+
function downloadBrowserWithProgressBarOutOfProcess(title: string, browserDirectory: string, url: string, executablePath: string | undefined, socketTimeout: number): Promise<{ error: Error | null }> {
7973
const cp = childProcess.fork(path.join(__dirname, 'oopDownloadBrowserMain.js'));
8074
const promise = new ManualPromise<{ error: Error | null }>();
8175
const progress = getDownloadProgress();
@@ -101,12 +95,11 @@ function downloadBrowserWithProgressBarOutOfProcess(title: string, browserDirect
10195

10296
debugLogger.log('install', `running download:`);
10397
debugLogger.log('install', `-- from url: ${url}`);
104-
debugLogger.log('install', `-- to location: ${zipPath}`);
98+
debugLogger.log('install', `-- to location: ${browserDirectory}`);
10599
const downloadParams: DownloadParams = {
106100
title,
107101
browserDirectory,
108102
url,
109-
zipPath,
110103
executablePath,
111104
socketTimeout,
112105
userAgent: getUserAgent(),

packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export type DownloadParams = {
2525
title: string;
2626
browserDirectory: string;
2727
url: string;
28-
zipPath: string;
2928
executablePath: string | undefined;
3029
socketTimeout: number;
3130
userAgent: string;
@@ -43,11 +42,11 @@ function browserDirectoryToMarkerFilePath(browserDirectory: string): string {
4342
return path.join(browserDirectory, 'INSTALLATION_COMPLETE');
4443
}
4544

46-
function downloadFile(options: DownloadParams): Promise<void> {
45+
function downloadFile(options: DownloadParams): Promise<Buffer> {
4746
let downloadedBytes = 0;
4847
let totalBytes = 0;
4948

50-
const promise = new ManualPromise<void>();
49+
const promise = new ManualPromise<Buffer>();
5150

5251
httpRequest({
5352
url: options.url,
@@ -73,21 +72,22 @@ function downloadFile(options: DownloadParams): Promise<void> {
7372
}
7473
totalBytes = parseInt(response.headers['content-length'] || '0', 10);
7574
log(`-- total bytes: ${totalBytes}`);
76-
const file = fs.createWriteStream(options.zipPath);
77-
file.on('finish', () => {
75+
const chunks: Buffer[] = [];
76+
response.on('end', () => {
7877
if (downloadedBytes !== totalBytes) {
7978
log(`-- download failed, size mismatch: ${downloadedBytes} != ${totalBytes}`);
8079
promise.reject(new Error(`Download failed: size mismatch, file size: ${downloadedBytes}, expected size: ${totalBytes} URL: ${options.url}`));
8180
} else {
8281
log(`-- download complete, size: ${downloadedBytes}`);
83-
promise.resolve();
82+
promise.resolve(Buffer.concat(chunks));
8483
}
8584
});
86-
file.on('error', error => promise.reject(error));
87-
response.pipe(file);
88-
response.on('data', onData);
85+
response.on('data', chunk => {
86+
chunks.push(chunk);
87+
downloadedBytes += chunk.length;
88+
progress(downloadedBytes, totalBytes);
89+
});
8990
response.on('error', (error: any) => {
90-
file.close();
9191
if (error?.code === 'ECONNRESET') {
9292
log(`-- download failed, server closed connection`);
9393
promise.reject(new Error(`Download failed: server closed connection. URL: ${options.url}`));
@@ -98,18 +98,13 @@ function downloadFile(options: DownloadParams): Promise<void> {
9898
});
9999
}, (error: any) => promise.reject(error));
100100
return promise;
101-
102-
function onData(chunk: string) {
103-
downloadedBytes += chunk.length;
104-
progress(downloadedBytes, totalBytes);
105-
}
106101
}
107102

108103
async function main(options: DownloadParams) {
109-
await downloadFile(options);
104+
const zipFile = await downloadFile(options);
110105
log(`SUCCESS downloading ${options.title}`);
111106
log(`extracting archive`);
112-
await extract(options.zipPath, { dir: options.browserDirectory });
107+
await extract(zipFile, { dir: options.browserDirectory });
113108
if (options.executablePath) {
114109
log(`fixing permissions at ${options.executablePath}`);
115110
await fs.promises.chmod(options.executablePath, 0o755);

0 commit comments

Comments
 (0)