diff --git a/docs/guides/system-requirements.md b/docs/guides/system-requirements.md index ffa4e50cf5e4d..1f0e9c6563d07 100644 --- a/docs/guides/system-requirements.md +++ b/docs/guides/system-requirements.md @@ -18,3 +18,4 @@ - Firefox browser system requirements: - https://www.mozilla.org/en-US/firefox/system-requirements/ + - The `xz` utility is required to unpack Firefox versions 135+ for Linux. diff --git a/packages/browsers/src/browser-data/firefox.ts b/packages/browsers/src/browser-data/firefox.ts index d5d4eb78302a6..afcf755e27d00 100644 --- a/packages/browsers/src/browser-data/firefox.ts +++ b/packages/browsers/src/browser-data/firefox.ts @@ -11,10 +11,15 @@ import {getJSON} from '../httpUtil.js'; import {BrowserPlatform, type ProfileOptions} from './types.js'; +function getFormat(buildId: string): string { + const majorVersion = Number(buildId.split('.').shift()!); + return majorVersion >= 135 ? 'xz' : 'bz2'; +} + function archiveNightly(platform: BrowserPlatform, buildId: string): string { switch (platform) { case BrowserPlatform.LINUX: - return `firefox-${buildId}.en-US.${platform}-x86_64.tar.bz2`; + return `firefox-${buildId}.en-US.${platform}-x86_64.tar.${getFormat(buildId)}`; case BrowserPlatform.MAC_ARM: case BrowserPlatform.MAC: return `firefox-${buildId}.en-US.mac.dmg`; @@ -27,7 +32,7 @@ function archiveNightly(platform: BrowserPlatform, buildId: string): string { function archive(platform: BrowserPlatform, buildId: string): string { switch (platform) { case BrowserPlatform.LINUX: - return `firefox-${buildId}.tar.bz2`; + return `firefox-${buildId}.tar.${getFormat(buildId)}`; case BrowserPlatform.MAC_ARM: case BrowserPlatform.MAC: return `Firefox ${buildId}.dmg`; diff --git a/packages/browsers/src/fileUtil.ts b/packages/browsers/src/fileUtil.ts index 15ff3e56dd3c6..e28328f5f0e5a 100644 --- a/packages/browsers/src/fileUtil.ts +++ b/packages/browsers/src/fileUtil.ts @@ -4,10 +4,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {spawnSync} from 'child_process'; +import {spawnSync, spawn} from 'child_process'; import {createReadStream} from 'fs'; import {mkdir, readdir} from 'fs/promises'; import * as path from 'path'; +import {Stream} from 'stream'; import extractZip from 'extract-zip'; import tar from 'tar-fs'; @@ -39,6 +40,8 @@ export async function unpackArchive( `Failed to extract ${archivePath} to ${folderPath}: ${result.output}`, ); } + } else if (archivePath.endsWith('.tar.xz')) { + await extractTarXz(archivePath, folderPath); } else { throw new Error(`Unsupported archive format: ${archivePath}`); } @@ -57,6 +60,63 @@ function extractTar(tarPath: string, folderPath: string): Promise { }); } +/** + * @internal + */ +function createXzStream() { + const child = spawn('xz', ['-d']); + const stream = new Stream.Transform({ + transform(chunk, encoding, callback) { + if (!child.stdin.write(chunk, encoding)) { + child.stdin.once('drain', callback); + } else { + callback(); + } + }, + + flush(callback) { + if (child.stdout.destroyed) { + callback(); + } else { + child.stdin.end(); + child.stdout.on('close', callback); + } + }, + }); + + child.stdin.on('error', e => { + if ('code' in e && e.code === 'EPIPE') { + // finished before reading the file finished (i.e. head) + stream.emit('end'); + } else { + stream.destroy(e); + } + }); + + child.stdout + .on('data', data => { + return stream.push(data); + }) + .on('error', e => { + return stream.destroy(e); + }); + + return stream; +} + +/** + * @internal + */ +function extractTarXz(tarPath: string, folderPath: string): Promise { + return new Promise((fulfill, reject) => { + const tarStream = tar.extract(folderPath); + tarStream.on('error', reject); + tarStream.on('finish', fulfill); + const readStream = createReadStream(tarPath); + readStream.pipe(createXzStream()).pipe(tarStream); + }); +} + /** * @internal */ diff --git a/packages/browsers/test/src/firefox/firefox-data.spec.ts b/packages/browsers/test/src/firefox/firefox-data.spec.ts index 6c58635f92af6..fbae5b270ac2c 100644 --- a/packages/browsers/test/src/firefox/firefox-data.spec.ts +++ b/packages/browsers/test/src/firefox/firefox-data.spec.ts @@ -23,6 +23,14 @@ describe('Firefox', () => { resolveDownloadUrl(BrowserPlatform.LINUX, '111.0a1'), 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.linux-x86_64.tar.bz2', ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, '135.0a1'), + 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-135.0a1.en-US.linux-x86_64.tar.xz', + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, '136.0a1'), + 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-136.0a1.en-US.linux-x86_64.tar.xz', + ); assert.strictEqual( resolveDownloadUrl(BrowserPlatform.MAC, '111.0a1'), 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.mac.dmg',