fix(browsers): various fixes and improvements (#9966)

This commit is contained in:
Alex Rudenko 2023-04-04 13:58:32 +02:00 committed by GitHub
parent fe934ad092
commit f1211cbec0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 15 deletions

View File

@ -20,7 +20,7 @@
},
"wireit": {
"build": {
"command": "tsc -b && chmod a+x lib/cjs/main-cli.js lib/esm/main-cli.js",
"command": "tsc -b && chmod a+x lib/cjs/main-cli.js lib/esm/main-cli.js && echo '{\"type\":\"module\"}' > lib/esm/package.json",
"files": [
"src/**/*.ts",
"tsconfig.json"

View File

@ -18,8 +18,9 @@ import {stdin as input, stdout as output} from 'process';
import * as readline from 'readline';
import ProgressBar from 'progress';
import yargs from 'yargs';
import type * as Yargs from 'yargs';
import {hideBin} from 'yargs/helpers';
import yargs from 'yargs/yargs';
import {
resolveBuildId,
@ -69,7 +70,7 @@ export class CLI {
this.#rl = rl;
}
#defineBrowserParameter(yargs: yargs.Argv<unknown>): void {
#defineBrowserParameter(yargs: Yargs.Argv<unknown>): void {
yargs.positional('browser', {
description:
'Which browser to install <browser>[@<buildId|latest>]. `latest` will try to find the latest available build. `buildId` is a browser-specific identifier such as a version or a revision.',
@ -83,7 +84,7 @@ export class CLI {
});
}
#definePlatformParameter(yargs: yargs.Argv<unknown>): void {
#definePlatformParameter(yargs: Yargs.Argv<unknown>): void {
yargs.option('platform', {
type: 'string',
desc: 'Platform that the binary needs to be compatible with.',
@ -92,7 +93,7 @@ export class CLI {
});
}
#definePathParameter(yargs: yargs.Argv<unknown>, required = false): void {
#definePathParameter(yargs: Yargs.Argv<unknown>, required = false): void {
yargs.option('path', {
type: 'string',
desc: 'Path to the root folder for the browser downloads and installation. The installation folder structure is compatible with the cache structure used by Puppeteer.',
@ -105,7 +106,8 @@ export class CLI {
}
async run(argv: string[]): Promise<void> {
await yargs(hideBin(argv))
const yargsInstance = yargs(hideBin(argv));
await yargsInstance
.scriptName('@puppeteer/browsers')
.command(
'install <browser>',
@ -254,7 +256,7 @@ export class CLI {
)
.demandCommand(1)
.help()
.wrap(Math.min(120, yargs.terminalWidth()))
.wrap(Math.min(120, yargsInstance.terminalWidth()))
.parse();
}

View File

@ -122,11 +122,12 @@ type LaunchOptions = {
pipe?: boolean;
dumpio?: boolean;
args?: string[];
env?: Record<string, string>;
env?: Record<string, string | undefined>;
handleSIGINT?: boolean;
handleSIGTERM?: boolean;
handleSIGHUP?: boolean;
detached?: boolean;
onExit?: () => Promise<void>;
};
export function launch(opts: LaunchOptions): Process {
@ -143,6 +144,11 @@ class Process {
#args: string[];
#browserProcess: childProcess.ChildProcess;
#exited = false;
// The browser process can be closed externally or from the driver process. We
// need to invoke the hooks only once though but we don't know how many times
// we will be invoked.
#hooksRan = false;
#onExitHook = async () => {};
#browserProcessExiting: Promise<void>;
constructor(opts: LaunchOptions) {
@ -190,15 +196,36 @@ class Process {
if (opts.handleSIGHUP) {
process.on('SIGHUP', this.#onDriverProcessSignal);
}
this.#browserProcessExiting = new Promise(resolve => {
this.#browserProcess.once('exit', () => {
this.#exited = true;
if (opts.onExit) {
this.#onExitHook = opts.onExit;
}
this.#browserProcessExiting = new Promise((resolve, reject) => {
this.#browserProcess.once('exit', async () => {
this.#clearListeners();
this.#exited = true;
try {
await this.#runHooks();
} catch (err) {
reject(err);
return;
}
resolve();
});
});
}
async #runHooks() {
if (this.#hooksRan) {
return;
}
this.#hooksRan = true;
await this.#onExitHook();
}
get nodeProcess(): childProcess.ChildProcess {
return this.#browserProcess;
}
#configureStdio(opts: {
pipe: boolean;
dumpio: boolean;
@ -236,19 +263,24 @@ class Process {
process.exit(130);
case 'SIGTERM':
case 'SIGHUP':
this.kill();
this.close();
break;
}
};
close(): Promise<void> {
async close(): Promise<void> {
await this.#runHooks();
if (this.#exited) {
return Promise.resolve();
return this.#browserProcessExiting;
}
this.kill();
return this.#browserProcessExiting;
}
hasClosed(): Promise<void> {
return this.#browserProcessExiting;
}
kill(): void {
// If the process failed to launch (for example if the browser executable path
// is invalid), then the process does not get a pid assigned. A call to
@ -329,6 +361,9 @@ class Process {
error ? ' ' + error.message : ''
}`,
stderr,
'',
'TROUBLESHOOTING: https://pptr.dev/troubleshooting',
'',
].join('\n')
)
);
@ -337,7 +372,7 @@ class Process {
function onTimeout(): void {
cleanup();
reject(
new Error(
new TimeoutError(
`Timed out after ${timeout} ms while waiting for the WS endpoint URL to appear in stdout!`
)
);
@ -403,3 +438,14 @@ export function isErrnoException(obj: unknown): obj is NodeJS.ErrnoException {
('errno' in obj || 'code' in obj || 'path' in obj || 'syscall' in obj)
);
}
export class TimeoutError extends Error {
/**
* @internal
*/
constructor(message?: string) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}

View File

@ -18,6 +18,7 @@ export {
launch,
computeExecutablePath,
computeSystemExecutablePath,
TimeoutError,
CDP_WEBSOCKET_ENDPOINT_REGEX,
WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX,
} from './launcher.js';
@ -28,6 +29,7 @@ export {
Browser,
BrowserPlatform,
ChromeReleaseChannel,
createProfile,
} from './browser-data/browser-data.js';
export {CLI, makeProgressCallback} from './CLI.js';
export {Cache} from './Cache.js';