chore: infrastructure for injecting scripts into DOMWorlds (#8801)

This commit is contained in:
jrandolf 2022-08-17 14:39:41 +02:00 committed by GitHub
parent a5f1078feb
commit bdcb748b98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1629 additions and 214 deletions

View File

@ -7,7 +7,7 @@ build/
lib/
# Generated files
tsconfig.tsbuildinfo
**/*.tsbuildinfo
puppeteer.api.json
puppeteer*.tgz
yarn.lock
@ -18,9 +18,11 @@ yarn.lock
test/output-*/
.dev_profile*
coverage/
src/generated
# IDE Artifacts
.vscode
.devcontainer
# Misc
.DS_Store
@ -32,6 +34,7 @@ coverage/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
## [END] Keep in sync with .gitignore
# ESLint ignores.

View File

@ -20,7 +20,7 @@ jobs:
run: npm install
- name: Build
run: |
node utils/generate_version_file.js
ts-node utils/generate_sources.ts
npm run docs
- name: Version docs
working-directory: ./website

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ yarn.lock
test/output-*/
.dev_profile*
coverage/
src/generated
# IDE Artifacts
.vscode

View File

@ -7,7 +7,7 @@ build/
lib/
# Generated files
tsconfig.tsbuildinfo
**/*.tsbuildinfo
puppeteer.api.json
puppeteer*.tgz
yarn.lock
@ -18,9 +18,11 @@ yarn.lock
test/output-*/
.dev_profile*
coverage/
src/generated
# IDE Artifacts
.vscode
.devcontainer
# Misc
.DS_Store

1117
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,8 +41,9 @@
"lint:prettier": "prettier --check .",
"lint:eslint": "([ \"$CI\" = true ] && eslint --ext js --ext ts --quiet -f codeframe . || eslint --ext js --ext ts .)",
"install": "node install.js",
"generate:sources": "ts-node utils/generate_sources.ts",
"generate:types": "node utils/export_all.js && api-extractor run --local --verbose && eslint --ext ts --no-ignore --no-eslintrc -c .eslintrc.types.cjs --fix lib/types.d.ts",
"generate:markdown": "ts-node -O '{\"module\":\"commonjs\"}' utils/generate_docs.ts && prettier --ignore-path none --write docs",
"generate:markdown": "ts-node utils/generate_docs.ts && prettier --ignore-path none --write docs",
"generate:esm-package-json": "echo '{\"type\": \"module\"}' > lib/esm/package.json",
"format": "run-s format:*",
"format:prettier": "prettier --write .",
@ -54,7 +55,7 @@
"check": "run-p check:*",
"check:protocol-revision": "ts-node -s scripts/ensure-correct-devtools-protocol-package",
"check:pinned-deps": "ts-node -s scripts/ensure-pinned-deps",
"build": "run-s build:tsc generate:types generate:esm-package-json",
"build": "run-s generate:sources build:tsc generate:types generate:esm-package-json",
"build:tsc": "tsc --version && run-p build:tsc:*",
"build:tsc:esm": "tsc -b src/tsconfig.esm.json",
"build:tsc:cjs": "tsc -b src/tsconfig.cjs.json",
@ -109,6 +110,7 @@
"commonmark": "0.30.0",
"cross-env": "7.0.3",
"diff": "5.1.0",
"esbuild": "0.15.5",
"eslint": "8.21.0",
"eslint-config-prettier": "8.5.0",
"eslint-formatter-codeframe": "7.32.1",

View File

@ -15,7 +15,7 @@
*/
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {
IsolatedWorld,

View File

@ -16,7 +16,7 @@
import {ChildProcess} from 'child_process';
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
import {EventEmitter} from './EventEmitter.js';
import {waitWithTimeout} from './util.js';

View File

@ -14,9 +14,10 @@
* limitations under the License.
*/
import {debugError, isErrorLike} from './util.js';
import {debugError} from './util.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {isNode} from '../environment.js';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {
Browser,
IsPageTargetCallback,

View File

@ -15,7 +15,7 @@
*/
import Protocol from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession, Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js';
import {Target} from './Target.js';

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {debug} from './Debug.js';
const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {addEventListener, debugError, PuppeteerEventListener} from './util.js';
import {Protocol} from 'devtools-protocol';
import {CDPSession} from './Connection.js';

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {Protocol} from 'devtools-protocol';

View File

@ -1,5 +1,5 @@
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js';
import {Frame, FrameManager} from './FrameManager.js';

View File

@ -15,7 +15,7 @@
*/
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {Frame} from './FrameManager.js';
import {IsolatedWorld} from './IsolatedWorld.js';

View File

@ -15,7 +15,7 @@
*/
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {ElementHandle} from './ElementHandle.js';
/**

View File

@ -15,7 +15,7 @@
*/
import Protocol from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession, Connection} from './Connection.js';
import {Target} from './Target.js';
import {TargetFilterCallback} from './Browser.js';

View File

@ -15,7 +15,7 @@
*/
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {EventEmitter} from './EventEmitter.js';
@ -35,12 +35,12 @@ import {Page} from './Page.js';
import {Target} from './Target.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {debugError} from './util.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {
createDeferredPromiseWithTimer,
debugError,
DeferredPromise,
isErrorLike,
} from './util.js';
} from '../util/DeferredPromise.js';
const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';

View File

@ -15,7 +15,7 @@
*/
import {Protocol} from 'devtools-protocol';
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {ProtocolError} from './Errors.js';
import {EventEmitter} from './EventEmitter.js';
import {Frame} from './FrameManager.js';

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js';
import {Protocol} from 'devtools-protocol';

View File

@ -15,7 +15,7 @@
*/
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {ElementHandle} from './ElementHandle.js';
import {TimeoutError} from './Errors.js';
@ -28,16 +28,18 @@ import {getQueryHandlerAndSelector} from './QueryHandler.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import {
createDeferredPromise,
createJSHandle,
debugError,
DeferredPromise,
importFS,
isNumber,
isString,
makePredicateString,
pageBindingInitString,
} from './util.js';
import {
createDeferredPromise,
DeferredPromise,
} from '../util/DeferredPromise.js';
// predicateQueryHandler and checkWaitForOptions are declared here so that
// TypeScript knows about them when used in the predicate function below.
@ -722,9 +724,7 @@ export class IsolatedWorld {
waitForVisible: boolean,
waitForHidden: boolean
): Promise<Node | null | boolean> {
const node = predicateQueryHandler
? ((await predicateQueryHandler(root, selector)) as Element)
: root.querySelector(selector);
const node = (await predicateQueryHandler(root, selector)) as Element;
return checkWaitForOptions(node, waitForVisible, waitForHidden);
}
const waitTaskOptions: WaitTaskOptions = {

View File

@ -15,7 +15,7 @@
*/
import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import type {ElementHandle} from './ElementHandle.js';
import {ExecutionContext} from './ExecutionContext.js';

View File

@ -14,14 +14,16 @@
* limitations under the License.
*/
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {
addEventListener,
PuppeteerEventListener,
removeEventListeners,
} from './util.js';
import {
DeferredPromise,
createDeferredPromise,
} from './util.js';
} from '../util/DeferredPromise.js';
import {TimeoutError} from './Errors.js';
import {
FrameManager,

View File

@ -16,18 +16,17 @@
import {Protocol} from 'devtools-protocol';
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {EventEmitter} from './EventEmitter.js';
import {Frame} from './FrameManager.js';
import {HTTPRequest} from './HTTPRequest.js';
import {HTTPResponse} from './HTTPResponse.js';
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
import {debugError, isString} from './util.js';
import {
debugError,
isString,
createDeferredPromiseWithTimer,
DeferredPromise,
} from './util.js';
} from '../util/DeferredPromise.js';
/**
* @public

View File

@ -17,7 +17,7 @@
import {Protocol} from 'devtools-protocol';
import type {Readable} from 'stream';
import {Accessibility} from './Accessibility.js';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {Browser, BrowserContext} from './Browser.js';
import {CDPSession, CDPSessionEmittedEvents} from './Connection.js';
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
@ -59,7 +59,6 @@ import {
importFS,
getReadableAsBuffer,
getReadableFromProtocolStream,
isErrorLike,
isNumber,
isString,
pageBindingDeliverErrorString,
@ -70,9 +69,12 @@ import {
valueFromRemoteObject,
waitForEvent,
waitWithTimeout,
} from './util.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {
createDeferredPromiseWithTimer,
DeferredPromise,
} from './util.js';
} from '../util/DeferredPromise.js';
import {WebWorker} from './WebWorker.js';
/**

View File

@ -13,12 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {assert} from './assert.js';
import {
getReadableAsBuffer,
getReadableFromProtocolStream,
isErrorLike,
} from './util.js';
import {assert} from '../util/assert.js';
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js';
/**

View File

@ -17,10 +17,11 @@
import {Protocol} from 'devtools-protocol';
import type {Readable} from 'stream';
import {isNode} from '../environment.js';
import {assert} from './assert.js';
import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {debug} from './Debug.js';
import {ElementHandle} from './ElementHandle.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {TimeoutError} from './Errors.js';
import {CommonEventEmitter} from './EventEmitter.js';
import {ExecutionContext} from './ExecutionContext.js';
@ -341,7 +342,7 @@ export function pageBindingDeliverErrorValueString(
*/
export function makePredicateString(
predicate: Function,
predicateQueryHandler?: Function
predicateQueryHandler: Function
): string {
function checkWaitForOptions(
node: Node | null,
@ -371,12 +372,10 @@ export function makePredicateString(
return !!(rect.top || rect.bottom || rect.width || rect.height);
}
}
const predicateQueryHandlerDef = predicateQueryHandler
? `const predicateQueryHandler = ${predicateQueryHandler};`
: '';
return `
(() => {
${predicateQueryHandlerDef}
const predicateQueryHandler = ${predicateQueryHandler};
const checkWaitForOptions = ${checkWaitForOptions};
return (${predicate})(...args)
})() `;
@ -496,104 +495,3 @@ export async function getReadableFromProtocolStream(
},
});
}
/**
* @internal
*/
export interface ErrorLike extends Error {
name: string;
message: string;
}
/**
* @internal
*/
export function isErrorLike(obj: unknown): obj is ErrorLike {
return (
typeof obj === 'object' && obj !== null && 'name' in obj && 'message' in obj
);
}
/**
* @internal
*/
export function isErrnoException(obj: unknown): obj is NodeJS.ErrnoException {
return (
isErrorLike(obj) &&
('errno' in obj || 'code' in obj || 'path' in obj || 'syscall' in obj)
);
}
/**
* @internal
*/
export interface DeferredPromise<T> extends Promise<T> {
resolved: () => boolean;
resolve: (_: T) => void;
reject: (_: Error) => void;
}
/**
* Creates an returns a promise along with the resolve/reject functions.
*
* If the promise has not been resolved/rejected withing the `timeout` period,
* the promise gets rejected with a timeout error.
*
* @internal
*/
export function createDeferredPromiseWithTimer<T>(
timeoutMessage: string,
timeout = 5000
): DeferredPromise<T> {
let isResolved = false;
let resolver = (_: T): void => {};
let rejector = (_: Error) => {};
const taskPromise = new Promise<T>((resolve, reject) => {
resolver = resolve;
rejector = reject;
});
const timeoutId = setTimeout(() => {
rejector(new TimeoutError(timeoutMessage));
}, timeout);
return Object.assign(taskPromise, {
resolved: () => {
return isResolved;
},
resolve: (value: T) => {
clearTimeout(timeoutId);
isResolved = true;
resolver(value);
},
reject: (err: Error) => {
clearTimeout(timeoutId);
rejector(err);
},
});
}
/**
* Creates an returns a promise along with the resolve/reject functions.
*
* @internal
*/
export function createDeferredPromise<T>(): DeferredPromise<T> {
let isResolved = false;
let resolver = (_: T): void => {};
let rejector = (_: Error) => {};
const taskPromise = new Promise<T>((resolve, reject) => {
resolver = resolve;
rejector = reject;
});
return Object.assign(taskPromise, {
resolved: () => {
return isResolved;
},
resolve: (value: T) => {
isResolved = true;
resolver(value);
},
reject: (err: Error) => {
rejector(err);
},
});
}

View File

@ -1,3 +0,0 @@
# Generated Artifacts
**Do not edit manually edit any TypeScript files in this folder** All TS files are generated from their respectively named template file (ext. `tmpl`) in the `templates` directory. Edit them there is needed.

View File

@ -1,4 +0,0 @@
/**
* @internal
*/
export const packageVersion = '16.1.1';

156
src/injected/Poller.ts Normal file
View File

@ -0,0 +1,156 @@
import {
createDeferredPromise,
DeferredPromise,
} from '../util/DeferredPromise.js';
import {assert} from '../util/assert.js';
interface Poller<T> {
start(): Promise<T>;
stop(): Promise<void>;
result(): Promise<T>;
}
export class MutationPoller<T> implements Poller<T> {
#fn: () => Promise<T>;
#root: Node;
#observer?: MutationObserver;
#promise?: DeferredPromise<T>;
constructor(fn: () => Promise<T>, root: Node) {
this.#fn = fn;
this.#root = root;
}
async start(): Promise<T> {
const promise = (this.#promise = createDeferredPromise<T>());
const result = await this.#fn();
if (result) {
promise.resolve(result);
return result;
}
this.#observer = new MutationObserver(async () => {
const result = await this.#fn();
if (!result) {
return;
}
promise.resolve(result);
await this.stop();
});
this.#observer.observe(this.#root, {
childList: true,
subtree: true,
attributes: true,
});
return this.#promise;
}
async stop(): Promise<void> {
assert(this.#promise, 'Polling never started.');
if (!this.#promise.finished()) {
this.#promise.reject(new Error('Polling stopped'));
}
if (this.#observer) {
this.#observer.disconnect();
}
}
result(): Promise<T> {
assert(this.#promise, 'Polling never started.');
return this.#promise;
}
}
export class RAFPoller<T> implements Poller<T> {
#fn: () => Promise<T>;
#promise?: DeferredPromise<T>;
constructor(fn: () => Promise<T>) {
this.#fn = fn;
}
async start(): Promise<T> {
const promise = (this.#promise = createDeferredPromise<T>());
const result = await this.#fn();
if (result) {
promise.resolve(result);
return result;
}
const poll = async () => {
if (promise.finished()) {
return;
}
const result = await this.#fn();
if (!result) {
window.requestAnimationFrame(poll);
return;
}
promise.resolve(result);
await this.stop();
};
window.requestAnimationFrame(poll);
return this.#promise;
}
async stop(): Promise<void> {
assert(this.#promise, 'Polling never started.');
if (!this.#promise.finished()) {
this.#promise.reject(new Error('Polling stopped'));
}
}
result(): Promise<T> {
assert(this.#promise, 'Polling never started.');
return this.#promise;
}
}
export class IntervalPoller<T> implements Poller<T> {
#fn: () => Promise<T>;
#ms: number;
#interval?: NodeJS.Timer;
#promise?: DeferredPromise<T>;
constructor(fn: () => Promise<T>, ms: number) {
this.#fn = fn;
this.#ms = ms;
}
async start(): Promise<T> {
const promise = (this.#promise = createDeferredPromise<T>());
const result = await this.#fn();
if (result) {
promise.resolve(result);
return result;
}
this.#interval = setInterval(async () => {
const result = await this.#fn();
if (!result) {
return;
}
promise.resolve(result);
await this.stop();
}, this.#ms);
return this.#promise;
}
async stop(): Promise<void> {
assert(this.#promise, 'Polling never started.');
if (!this.#promise.finished()) {
this.#promise.reject(new Error('Polling stopped'));
}
if (this.#interval) {
clearInterval(this.#interval);
}
}
result(): Promise<T> {
assert(this.#promise, 'Polling never started.');
return this.#promise;
}
}

5
src/injected/README.md Normal file
View File

@ -0,0 +1,5 @@
# Injected
This folder contains code that is injected into every Puppeteer execution context. Each file is transpiled using esbuild into a script in `src/generated` which is then imported into server code.
See `utils/generate_injected.ts` for more information.

1
src/injected/injected.ts Normal file
View File

@ -0,0 +1 @@
export * from './Poller.js';

View File

@ -33,7 +33,7 @@ import createHttpsProxyAgent, {
HttpsProxyAgentOptions,
} from 'https-proxy-agent';
import {getProxyForUrl} from 'proxy-from-env';
import {assert} from '../common/assert.js';
import {assert} from '../util/assert.js';
import tar from 'tar-fs';
import bzip from 'unbzip2-stream';

View File

@ -20,18 +20,17 @@ import * as path from 'path';
import * as readline from 'readline';
import removeFolder from 'rimraf';
import {promisify} from 'util';
import {assert} from '../common/assert.js';
import {assert} from '../util/assert.js';
import {Connection} from '../common/Connection.js';
import {debug} from '../common/Debug.js';
import {TimeoutError} from '../common/Errors.js';
import {
debugError,
addEventListener,
isErrnoException,
isErrorLike,
PuppeteerEventListener,
removeEventListeners,
} from '../common/util.js';
import {isErrnoException, isErrorLike} from '../util/ErrorLike.js';
import {Product} from '../common/Product.js';
import {NodeWebSocketTransport as WebSocketTransport} from '../node/NodeWebSocketTransport.js';
import {LaunchOptions} from './LaunchOptions.js';

View File

@ -1,6 +1,6 @@
import fs from 'fs';
import path from 'path';
import {assert} from '../common/assert.js';
import {assert} from '../util/assert.js';
import {Browser} from '../common/Browser.js';
import {Product} from '../common/Product.js';
import {BrowserRunner} from './BrowserRunner.js';

View File

@ -1,7 +1,7 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import {assert} from '../common/assert.js';
import {assert} from '../util/assert.js';
import {Browser} from '../common/Browser.js';
import {Product} from '../common/Product.js';
import {BrowserFetcher} from './BrowserFetcher.js';

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {assert} from '../common/assert.js';
import {assert} from '../util/assert.js';
import {ConnectionTransport} from '../common/ConnectionTransport.js';
import {
addEventListener,

3
src/templates/README.md Normal file
View File

@ -0,0 +1,3 @@
# Templated Artifacts
These files are generated as TypeScript files in the `src/generated` folder.

View File

@ -52,7 +52,6 @@ export * from './common/TimeoutSettings.js';
export * from './common/Tracing.js';
export * from './common/USKeyboardLayout.js';
export * from './common/WebWorker.js';
export * from './common/assert.js';
export * from './common/fetch.js';
export * from './common/types.js';
export * from './common/util.js';
@ -71,4 +70,5 @@ export * from './node/install.js';
export * from './node/util.js';
// Exports from `generated`
export * from './generated/injected.js';
export * from './generated/version.js';

View File

@ -0,0 +1,88 @@
import {TimeoutError} from '../common/Errors.js';
/**
* @internal
*/
export interface DeferredPromise<T> extends Promise<T> {
finished: () => boolean;
resolved: () => boolean;
resolve: (_: T) => void;
reject: (_: Error) => void;
}
/**
* Creates an returns a promise along with the resolve/reject functions.
*
* If the promise has not been resolved/rejected withing the `timeout` period,
* the promise gets rejected with a timeout error.
*
* @internal
*/
export function createDeferredPromiseWithTimer<T>(
timeoutMessage: string,
timeout = 5000
): DeferredPromise<T> {
let isResolved = false;
let isRejected = false;
let resolver = (_: T): void => {};
let rejector = (_: Error) => {};
const taskPromise = new Promise<T>((resolve, reject) => {
resolver = resolve;
rejector = reject;
});
const timeoutId = setTimeout(() => {
isRejected = true;
rejector(new TimeoutError(timeoutMessage));
}, timeout);
return Object.assign(taskPromise, {
resolved: () => {
return isResolved;
},
finished: () => {
return isResolved || isRejected;
},
resolve: (value: T) => {
clearTimeout(timeoutId);
isResolved = true;
resolver(value);
},
reject: (err: Error) => {
clearTimeout(timeoutId);
isRejected = true;
rejector(err);
},
});
}
/**
* Creates an returns a promise along with the resolve/reject functions.
*
* @internal
*/
export function createDeferredPromise<T>(): DeferredPromise<T> {
let isResolved = false;
let isRejected = false;
let resolver = (_: T): void => {};
let rejector = (_: Error) => {};
const taskPromise = new Promise<T>((resolve, reject) => {
resolver = resolve;
rejector = reject;
});
return Object.assign(taskPromise, {
resolved: () => {
return isResolved;
},
finished: () => {
return isResolved || isRejected;
},
resolve: (value: T) => {
isResolved = true;
resolver(value);
},
reject: (err: Error) => {
isRejected = true;
rejector(err);
},
});
}

27
src/util/ErrorLike.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* @internal
*/
export interface ErrorLike extends Error {
name: string;
message: string;
}
/**
* @internal
*/
export function isErrorLike(obj: unknown): obj is ErrorLike {
return (
typeof obj === 'object' && obj !== null && 'name' in obj && 'message' in obj
);
}
/**
* @internal
*/
export function isErrnoException(obj: unknown): obj is NodeJS.ErrnoException {
return (
isErrorLike(obj) &&
('errno' in obj || 'code' in obj || 'path' in obj || 'syscall' in obj)
);
}

View File

@ -22,7 +22,7 @@ import {
setupTestPageAndContextHooks,
describeChromeOnly,
} from './mocha-utils.js';
import {isErrorLike} from '../../lib/cjs/puppeteer/common/util.js';
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
describeChromeOnly('Target.createCDPSession', function () {
setupTestBrowserHooks();

View File

@ -26,7 +26,7 @@ import {
BrowserContext,
} from '../../lib/cjs/puppeteer/common/Browser.js';
import {Page} from '../../lib/cjs/puppeteer/common/Page.js';
import {isErrorLike} from '../../lib/cjs/puppeteer/common/util.js';
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
import {
PuppeteerLaunchOptions,
PuppeteerNode,

View File

@ -15,7 +15,7 @@
*/
import expect from 'expect';
import {isErrorLike} from '../../lib/cjs/puppeteer/common/util.js';
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
import {
getTestState,
itFailsFirefox,

View File

@ -45,7 +45,7 @@ const fileExists = async filePath => {
* place.
*/
async function compileTypeScript() {
return exec('npm run build:tsc').catch(error => {
return exec('npm run build').catch(error => {
console.error('Error running TypeScript', error);
process.exit(1);
});

6
utils/.eslintrc.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
extends: ['../.eslintrc.js'],
rules: {
'import/extensions': 0,
},
};

View File

@ -20,8 +20,6 @@ import {chdir} from 'process';
import semver from 'semver';
import {versionsPerRelease} from '../versions.js';
import versionsArchived from '../website/versionsArchived.json';
// eslint-disable-next-line import/extensions
import {generateDocs} from './internal/custom_markdown_action';
function getOffsetAndLimit(

69
utils/generate_sources.ts Normal file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env node
import esbuild from 'esbuild';
import {mkdir, mkdtemp, readFile, writeFile} from 'fs/promises';
import path from 'path';
import rimraf from 'rimraf';
import {job} from './internal/job';
(async () => {
await job('', async ({outputs}) => {
await Promise.all(
outputs.map(outputs => {
return mkdir(outputs, {recursive: true});
})
);
})
.outputs(['src/generated'])
.build();
await job('', async ({name, inputs, outputs}) => {
const tmp = await mkdtemp(name);
await esbuild.build({
entryPoints: [inputs[0]!],
bundle: true,
outdir: tmp,
format: 'cjs',
platform: 'browser',
target: 'ES2019',
});
const baseName = path.basename(inputs[0]!);
const content = await readFile(
path.join(tmp, baseName.replace('.ts', '.js')),
'utf-8'
);
const scriptContent = `/** @internal */
export const source = ${JSON.stringify(content)};
`;
await writeFile(outputs[0]!, scriptContent);
await rimraf.sync(tmp);
})
.inputs(['src/injected/**.ts'])
.outputs(['src/generated/injected.ts'])
.build();
job('', async ({inputs, outputs}) => {
const version = JSON.parse(await readFile(inputs[0]!, 'utf8')).version;
await writeFile(
outputs[0]!,
(
await readFile(outputs[0]!, {
encoding: 'utf-8',
})
).replace("'NEXT'", `v${version}`)
);
})
.inputs(['package.json'])
.outputs(['versions.js'])
.build();
job('', async ({inputs, outputs}) => {
const version = JSON.parse(await readFile(inputs[0]!, 'utf8')).version;
await writeFile(
outputs[0]!,
(await readFile(inputs[1]!, 'utf8')).replace('PACKAGE_VERSION', version)
);
})
.inputs(['package.json', 'src/templates/version.ts.tmpl'])
.outputs(['src/generated/version.ts'])
.build();
})();

View File

@ -1,18 +0,0 @@
const {writeFileSync, readFileSync} = require('fs');
const {join} = require('path');
const version = require('../package.json').version;
writeFileSync(
join(__dirname, '../src/generated/version.ts'),
readFileSync(join(__dirname, '../src/templates/version.ts.tmpl'), {
encoding: 'utf-8',
}).replace('PACKAGE_VERSION', version)
);
writeFileSync(
join(__dirname, '../versions.js'),
readFileSync(join(__dirname, '../versions.js'), {
encoding: 'utf-8',
}).replace('NEXT', `v${version}`)
);

96
utils/internal/job.ts Normal file
View File

@ -0,0 +1,96 @@
import {Stats} from 'fs';
import {stat} from 'fs/promises';
import {glob} from 'glob';
import path from 'path';
interface JobContext {
name: string;
inputs: string[];
outputs: string[];
}
class JobBuilder {
#inputs: string[] = [];
#outputs: string[] = [];
#callback: (ctx: JobContext) => Promise<void>;
#name: string;
constructor(name: string, callback: (ctx: JobContext) => Promise<void>) {
this.#name = name;
this.#callback = callback;
}
inputs(inputs: string[]): JobBuilder {
this.#inputs = inputs.flatMap(value => {
value = path.resolve(__dirname, '..', '..', value);
const paths = glob.sync(value);
return paths.length ? paths : [value];
});
return this;
}
outputs(outputs: string[]): JobBuilder {
if (!this.#name) {
this.#name = outputs[0]!;
}
this.#outputs = outputs.map(value => {
return path.resolve(__dirname, '..', '..', value);
});
return this;
}
async build(): Promise<void> {
console.log(`Running job ${this.#name}...`);
let shouldRun = true;
const inputStats = await Promise.all(
this.#inputs.map(input => {
return stat(input);
})
);
let outputStats: Stats[];
try {
outputStats = await Promise.all(
this.#outputs.map(output => {
return stat(output);
})
);
if (
outputStats.reduce(reduceMaxTime, 0) >=
inputStats.reduce(reduceMinTime, Infinity)
) {
shouldRun = false;
}
} catch {}
if (shouldRun) {
this.#run();
}
}
#run(): Promise<void> {
return this.#callback({
name: this.#name,
inputs: this.#inputs,
outputs: this.#outputs,
});
}
}
export const job = (
name: string,
callback: (ctx: JobContext) => Promise<void>
): JobBuilder => {
return new JobBuilder(name, callback);
};
const reduceMaxTime = (time: number, stat: Stats) => {
return time < stat.mtimeMs ? stat.mtimeMs : time;
};
const reduceMinTime = (time: number, stat: Stats) => {
return time > stat.mtimeMs ? stat.mtimeMs : time;
};

12
utils/tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
/**
* This configuration only exists for the API Extractor tool and for VSCode to use. It is NOT the tsconfig used for compilation.
* For CJS builds, `tsconfig.cjs.json` is used, and for ESM, it's `tsconfig.esm.json`.
* See the details in CONTRIBUTING.md that describes our TypeScript setup.
*/
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"module": "CommonJS"
}
}

View File

@ -16,7 +16,7 @@
const versionsPerRelease = new Map([
// This is a mapping from Chromium version => Puppeteer version.
// In Chromium roll patches, use 'v16.1.1' for the Puppeteer version.
// In Chromium roll patches, use `NEXT` for the Puppeteer version.
['105.0.5173.0', 'v15.5.0'],
['104.0.5109.0', 'v15.1.0'],
['103.0.5059.0', 'v14.2.0'],