Update dashboard, kb, memory +4 more (+28 ~18 -1)
This commit is contained in:
551
node_modules/puppeteer-core/src/node/BrowserLauncher.ts
generated
vendored
Normal file
551
node_modules/puppeteer-core/src/node/BrowserLauncher.ts
generated
vendored
Normal file
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import {existsSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
|
||||
import {
|
||||
Browser as InstalledBrowser,
|
||||
CDP_WEBSOCKET_ENDPOINT_REGEX,
|
||||
launch,
|
||||
TimeoutError as BrowsersTimeoutError,
|
||||
WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX,
|
||||
computeExecutablePath,
|
||||
} from '@puppeteer/browsers';
|
||||
|
||||
import {
|
||||
firstValueFrom,
|
||||
from,
|
||||
map,
|
||||
race,
|
||||
timer,
|
||||
} from '../../third_party/rxjs/rxjs.js';
|
||||
import type {Browser, BrowserCloseCallback} from '../api/Browser.js';
|
||||
import {CdpBrowser} from '../cdp/Browser.js';
|
||||
import {Connection} from '../cdp/Connection.js';
|
||||
import {TimeoutError} from '../common/Errors.js';
|
||||
import type {SupportedBrowser} from '../common/SupportedBrowser.js';
|
||||
import {debugError, DEFAULT_VIEWPORT} from '../common/util.js';
|
||||
import type {Viewport} from '../common/Viewport.js';
|
||||
import {
|
||||
createIncrementalIdGenerator,
|
||||
type GetIdFn,
|
||||
} from '../util/incremental-id-generator.js';
|
||||
|
||||
import type {ChromeReleaseChannel, LaunchOptions} from './LaunchOptions.js';
|
||||
import {NodeWebSocketTransport as WebSocketTransport} from './NodeWebSocketTransport.js';
|
||||
import {PipeTransport} from './PipeTransport.js';
|
||||
import type {PuppeteerNode} from './PuppeteerNode.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface ResolvedLaunchArgs {
|
||||
isTempUserDataDir: boolean;
|
||||
userDataDir: string;
|
||||
executablePath: string;
|
||||
args: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a launcher - a class that is able to create and launch a browser instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export abstract class BrowserLauncher {
|
||||
#browser: SupportedBrowser;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
puppeteer: PuppeteerNode;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(puppeteer: PuppeteerNode, browser: SupportedBrowser) {
|
||||
this.puppeteer = puppeteer;
|
||||
this.#browser = browser;
|
||||
}
|
||||
|
||||
get browser(): SupportedBrowser {
|
||||
return this.#browser;
|
||||
}
|
||||
|
||||
async launch(options: LaunchOptions = {}): Promise<Browser> {
|
||||
const {
|
||||
dumpio = false,
|
||||
enableExtensions = false,
|
||||
env = process.env,
|
||||
handleSIGINT = true,
|
||||
handleSIGTERM = true,
|
||||
handleSIGHUP = true,
|
||||
acceptInsecureCerts = false,
|
||||
networkEnabled = true,
|
||||
defaultViewport = DEFAULT_VIEWPORT,
|
||||
downloadBehavior,
|
||||
slowMo = 0,
|
||||
timeout = 30000,
|
||||
waitForInitialPage = true,
|
||||
protocolTimeout,
|
||||
handleDevToolsAsPage,
|
||||
idGenerator = createIncrementalIdGenerator(),
|
||||
} = options;
|
||||
|
||||
let {protocol} = options;
|
||||
|
||||
// Default to 'webDriverBiDi' for Firefox.
|
||||
if (this.#browser === 'firefox' && protocol === undefined) {
|
||||
protocol = 'webDriverBiDi';
|
||||
}
|
||||
|
||||
if (this.#browser === 'firefox' && protocol === 'cdp') {
|
||||
throw new Error('Connecting to Firefox using CDP is no longer supported');
|
||||
}
|
||||
|
||||
const launchArgs = await this.computeLaunchArguments({
|
||||
...options,
|
||||
protocol,
|
||||
});
|
||||
|
||||
if (!existsSync(launchArgs.executablePath)) {
|
||||
throw new Error(
|
||||
`Browser was not found at the configured executablePath (${launchArgs.executablePath})`,
|
||||
);
|
||||
}
|
||||
|
||||
const usePipe = launchArgs.args.includes('--remote-debugging-pipe');
|
||||
|
||||
const onProcessExit = async () => {
|
||||
await this.cleanUserDataDir(launchArgs.userDataDir, {
|
||||
isTemp: launchArgs.isTempUserDataDir,
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
this.#browser === 'firefox' &&
|
||||
protocol === 'webDriverBiDi' &&
|
||||
usePipe
|
||||
) {
|
||||
throw new Error(
|
||||
'Pipe connections are not supported with Firefox and WebDriver BiDi',
|
||||
);
|
||||
}
|
||||
|
||||
const browserProcess = launch({
|
||||
executablePath: launchArgs.executablePath,
|
||||
args: launchArgs.args,
|
||||
handleSIGHUP,
|
||||
handleSIGTERM,
|
||||
handleSIGINT,
|
||||
dumpio,
|
||||
env,
|
||||
pipe: usePipe,
|
||||
onExit: onProcessExit,
|
||||
signal: options.signal,
|
||||
});
|
||||
|
||||
let browser: Browser;
|
||||
let cdpConnection: Connection;
|
||||
let closing = false;
|
||||
|
||||
const browserCloseCallback: BrowserCloseCallback = async () => {
|
||||
if (closing) {
|
||||
return;
|
||||
}
|
||||
closing = true;
|
||||
await this.closeBrowser(browserProcess, cdpConnection);
|
||||
};
|
||||
|
||||
try {
|
||||
if (this.#browser === 'firefox') {
|
||||
browser = await this.createBiDiBrowser(
|
||||
browserProcess,
|
||||
browserCloseCallback,
|
||||
{
|
||||
timeout,
|
||||
protocolTimeout,
|
||||
slowMo,
|
||||
defaultViewport,
|
||||
acceptInsecureCerts,
|
||||
networkEnabled,
|
||||
idGenerator,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (usePipe) {
|
||||
cdpConnection = await this.createCdpPipeConnection(browserProcess, {
|
||||
timeout,
|
||||
protocolTimeout,
|
||||
slowMo,
|
||||
idGenerator,
|
||||
});
|
||||
} else {
|
||||
cdpConnection = await this.createCdpSocketConnection(browserProcess, {
|
||||
timeout,
|
||||
protocolTimeout,
|
||||
slowMo,
|
||||
idGenerator,
|
||||
});
|
||||
}
|
||||
|
||||
if (protocol === 'webDriverBiDi') {
|
||||
browser = await this.createBiDiOverCdpBrowser(
|
||||
browserProcess,
|
||||
cdpConnection,
|
||||
browserCloseCallback,
|
||||
{
|
||||
defaultViewport,
|
||||
acceptInsecureCerts,
|
||||
networkEnabled,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
browser = await CdpBrowser._create(
|
||||
cdpConnection,
|
||||
[],
|
||||
acceptInsecureCerts,
|
||||
defaultViewport,
|
||||
downloadBehavior,
|
||||
browserProcess.nodeProcess,
|
||||
browserCloseCallback,
|
||||
options.targetFilter,
|
||||
undefined,
|
||||
undefined,
|
||||
networkEnabled,
|
||||
handleDevToolsAsPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
void browserCloseCallback();
|
||||
const logs = browserProcess.getRecentLogs().join('\n');
|
||||
if (
|
||||
logs.includes(
|
||||
'Failed to create a ProcessSingleton for your profile directory',
|
||||
) ||
|
||||
// On Windows we will not get logs due to the singleton process
|
||||
// handover. See
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/process_singleton_win.cc;l=46;drc=fc7952f0422b5073515a205a04ec9c3a1ae81658
|
||||
(process.platform === 'win32' &&
|
||||
existsSync(join(launchArgs.userDataDir, 'lockfile')))
|
||||
) {
|
||||
throw new Error(
|
||||
`The browser is already running for ${launchArgs.userDataDir}. Use a different \`userDataDir\` or stop the running browser first.`,
|
||||
);
|
||||
}
|
||||
if (logs.includes('Missing X server') && options.headless === false) {
|
||||
throw new Error(
|
||||
`Missing X server to start the headful browser. Either set headless to true or use xvfb-run to run your Puppeteer script.`,
|
||||
);
|
||||
}
|
||||
if (error instanceof BrowsersTimeoutError) {
|
||||
throw new TimeoutError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (Array.isArray(enableExtensions)) {
|
||||
if (this.#browser === 'chrome' && !usePipe) {
|
||||
throw new Error(
|
||||
'To use `enableExtensions` with a list of paths in Chrome, you must be connected with `--remote-debugging-pipe` (`pipe: true`).',
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
enableExtensions.map(path => {
|
||||
return browser.installExtension(path);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
if (waitForInitialPage) {
|
||||
await this.waitForPageTarget(browser, timeout);
|
||||
}
|
||||
|
||||
return browser;
|
||||
}
|
||||
|
||||
abstract executablePath(
|
||||
channel?: ChromeReleaseChannel,
|
||||
validatePath?: boolean,
|
||||
): string;
|
||||
|
||||
abstract defaultArgs(object: LaunchOptions): string[];
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected abstract computeLaunchArguments(
|
||||
options: LaunchOptions,
|
||||
): Promise<ResolvedLaunchArgs>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected abstract cleanUserDataDir(
|
||||
path: string,
|
||||
opts: {isTemp: boolean},
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async closeBrowser(
|
||||
browserProcess: ReturnType<typeof launch>,
|
||||
cdpConnection?: Connection,
|
||||
): Promise<void> {
|
||||
if (cdpConnection) {
|
||||
// Attempt to close the browser gracefully
|
||||
try {
|
||||
await cdpConnection.closeBrowser();
|
||||
await browserProcess.hasClosed();
|
||||
} catch (error) {
|
||||
debugError(error);
|
||||
await browserProcess.close();
|
||||
}
|
||||
} else {
|
||||
// Wait for a possible graceful shutdown.
|
||||
await firstValueFrom(
|
||||
race(
|
||||
from(browserProcess.hasClosed()),
|
||||
timer(5000).pipe(
|
||||
map(() => {
|
||||
return from(browserProcess.close());
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async waitForPageTarget(
|
||||
browser: Browser,
|
||||
timeout: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await browser.waitForTarget(
|
||||
t => {
|
||||
return t.type() === 'page';
|
||||
},
|
||||
{timeout},
|
||||
);
|
||||
} catch (error) {
|
||||
await browser.close();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async createCdpSocketConnection(
|
||||
browserProcess: ReturnType<typeof launch>,
|
||||
opts: {
|
||||
timeout: number;
|
||||
protocolTimeout: number | undefined;
|
||||
slowMo: number;
|
||||
idGenerator: GetIdFn;
|
||||
},
|
||||
): Promise<Connection> {
|
||||
const browserWSEndpoint = await browserProcess.waitForLineOutput(
|
||||
CDP_WEBSOCKET_ENDPOINT_REGEX,
|
||||
opts.timeout,
|
||||
);
|
||||
const transport = await WebSocketTransport.create(browserWSEndpoint);
|
||||
return new Connection(
|
||||
browserWSEndpoint,
|
||||
transport,
|
||||
opts.slowMo,
|
||||
opts.protocolTimeout,
|
||||
/* rawErrors */ false,
|
||||
opts.idGenerator,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async createCdpPipeConnection(
|
||||
browserProcess: ReturnType<typeof launch>,
|
||||
opts: {
|
||||
timeout: number;
|
||||
protocolTimeout: number | undefined;
|
||||
slowMo: number;
|
||||
idGenerator: GetIdFn;
|
||||
},
|
||||
): Promise<Connection> {
|
||||
// stdio was assigned during start(), and the 'pipe' option there adds the
|
||||
// 4th and 5th items to stdio array
|
||||
const {3: pipeWrite, 4: pipeRead} = browserProcess.nodeProcess.stdio;
|
||||
const transport = new PipeTransport(
|
||||
pipeWrite as NodeJS.WritableStream,
|
||||
pipeRead as NodeJS.ReadableStream,
|
||||
);
|
||||
return new Connection(
|
||||
'',
|
||||
transport,
|
||||
opts.slowMo,
|
||||
opts.protocolTimeout,
|
||||
/* rawErrors */ false,
|
||||
opts.idGenerator,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async createBiDiOverCdpBrowser(
|
||||
browserProcess: ReturnType<typeof launch>,
|
||||
cdpConnection: Connection,
|
||||
closeCallback: BrowserCloseCallback,
|
||||
opts: {
|
||||
defaultViewport: Viewport | null;
|
||||
acceptInsecureCerts?: boolean;
|
||||
networkEnabled: boolean;
|
||||
},
|
||||
): Promise<Browser> {
|
||||
const bidiOnly = process.env['PUPPETEER_WEBDRIVER_BIDI_ONLY'] === 'true';
|
||||
const BiDi = await import(/* webpackIgnore: true */ '../bidi/bidi.js');
|
||||
const bidiConnection = await BiDi.connectBidiOverCdp(cdpConnection);
|
||||
return await BiDi.BidiBrowser.create({
|
||||
connection: bidiConnection,
|
||||
// Do not provide CDP connection to Browser, if BiDi-only mode is enabled. This
|
||||
// would restrict Browser to use only BiDi endpoint.
|
||||
cdpConnection: bidiOnly ? undefined : cdpConnection,
|
||||
closeCallback,
|
||||
process: browserProcess.nodeProcess,
|
||||
defaultViewport: opts.defaultViewport,
|
||||
acceptInsecureCerts: opts.acceptInsecureCerts,
|
||||
networkEnabled: opts.networkEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async createBiDiBrowser(
|
||||
browserProcess: ReturnType<typeof launch>,
|
||||
closeCallback: BrowserCloseCallback,
|
||||
opts: {
|
||||
timeout: number;
|
||||
protocolTimeout: number | undefined;
|
||||
slowMo: number;
|
||||
idGenerator: GetIdFn;
|
||||
defaultViewport: Viewport | null;
|
||||
acceptInsecureCerts?: boolean;
|
||||
networkEnabled?: boolean;
|
||||
},
|
||||
): Promise<Browser> {
|
||||
const browserWSEndpoint =
|
||||
(await browserProcess.waitForLineOutput(
|
||||
WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX,
|
||||
opts.timeout,
|
||||
)) + '/session';
|
||||
const transport = await WebSocketTransport.create(browserWSEndpoint);
|
||||
const BiDi = await import(/* webpackIgnore: true */ '../bidi/bidi.js');
|
||||
const bidiConnection = new BiDi.BidiConnection(
|
||||
browserWSEndpoint,
|
||||
transport,
|
||||
opts.idGenerator,
|
||||
opts.slowMo,
|
||||
opts.protocolTimeout,
|
||||
);
|
||||
return await BiDi.BidiBrowser.create({
|
||||
connection: bidiConnection,
|
||||
closeCallback,
|
||||
process: browserProcess.nodeProcess,
|
||||
defaultViewport: opts.defaultViewport,
|
||||
acceptInsecureCerts: opts.acceptInsecureCerts,
|
||||
networkEnabled: opts.networkEnabled ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected getProfilePath(): string {
|
||||
return join(
|
||||
this.puppeteer.configuration.temporaryDirectory ?? tmpdir(),
|
||||
`puppeteer_dev_${this.browser}_profile-`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
resolveExecutablePath(
|
||||
headless?: boolean | 'shell',
|
||||
validatePath = true,
|
||||
): string {
|
||||
let executablePath = this.puppeteer.configuration.executablePath;
|
||||
if (executablePath) {
|
||||
if (validatePath && !existsSync(executablePath)) {
|
||||
throw new Error(
|
||||
`Tried to find the browser at the configured path (${executablePath}), but no executable was found.`,
|
||||
);
|
||||
}
|
||||
return executablePath;
|
||||
}
|
||||
|
||||
function puppeteerBrowserToInstalledBrowser(
|
||||
browser?: SupportedBrowser,
|
||||
headless?: boolean | 'shell',
|
||||
) {
|
||||
switch (browser) {
|
||||
case 'chrome':
|
||||
if (headless === 'shell') {
|
||||
return InstalledBrowser.CHROMEHEADLESSSHELL;
|
||||
}
|
||||
return InstalledBrowser.CHROME;
|
||||
case 'firefox':
|
||||
return InstalledBrowser.FIREFOX;
|
||||
}
|
||||
return InstalledBrowser.CHROME;
|
||||
}
|
||||
|
||||
const browserType = puppeteerBrowserToInstalledBrowser(
|
||||
this.browser,
|
||||
headless,
|
||||
);
|
||||
|
||||
executablePath = computeExecutablePath({
|
||||
cacheDir: this.puppeteer.defaultDownloadPath!,
|
||||
browser: browserType,
|
||||
buildId: this.puppeteer.browserVersion,
|
||||
});
|
||||
|
||||
if (validatePath && !existsSync(executablePath)) {
|
||||
const configVersion =
|
||||
this.puppeteer.configuration?.[this.browser]?.version;
|
||||
if (configVersion) {
|
||||
throw new Error(
|
||||
`Tried to find the browser at the configured path (${executablePath}) for version ${configVersion}, but no executable was found.`,
|
||||
);
|
||||
}
|
||||
switch (this.browser) {
|
||||
case 'chrome':
|
||||
throw new Error(
|
||||
`Could not find Chrome (ver. ${this.puppeteer.browserVersion}). This can occur if either\n` +
|
||||
` 1. you did not perform an installation before running the script (e.g. \`npx puppeteer browsers install ${browserType}\`) or\n` +
|
||||
` 2. your cache path is incorrectly configured (which is: ${this.puppeteer.configuration.cacheDirectory}).\n` +
|
||||
'For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.',
|
||||
);
|
||||
case 'firefox':
|
||||
throw new Error(
|
||||
`Could not find Firefox (rev. ${this.puppeteer.browserVersion}). This can occur if either\n` +
|
||||
' 1. you did not perform an installation for Firefox before running the script (e.g. `npx puppeteer browsers install firefox`) or\n' +
|
||||
` 2. your cache path is incorrectly configured (which is: ${this.puppeteer.configuration.cacheDirectory}).\n` +
|
||||
'For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.',
|
||||
);
|
||||
}
|
||||
}
|
||||
return executablePath;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user