diff options
Diffstat (limited to 'remote/test/puppeteer/src/injected')
-rw-r--r-- | remote/test/puppeteer/src/injected/Poller.ts | 156 | ||||
-rw-r--r-- | remote/test/puppeteer/src/injected/README.md | 5 | ||||
-rw-r--r-- | remote/test/puppeteer/src/injected/injected.ts | 14 | ||||
-rw-r--r-- | remote/test/puppeteer/src/injected/util.ts | 18 |
4 files changed, 193 insertions, 0 deletions
diff --git a/remote/test/puppeteer/src/injected/Poller.ts b/remote/test/puppeteer/src/injected/Poller.ts new file mode 100644 index 0000000000..c6748ebbc4 --- /dev/null +++ b/remote/test/puppeteer/src/injected/Poller.ts @@ -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; + } +} diff --git a/remote/test/puppeteer/src/injected/README.md b/remote/test/puppeteer/src/injected/README.md new file mode 100644 index 0000000000..0a4af11cce --- /dev/null +++ b/remote/test/puppeteer/src/injected/README.md @@ -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. diff --git a/remote/test/puppeteer/src/injected/injected.ts b/remote/test/puppeteer/src/injected/injected.ts new file mode 100644 index 0000000000..7650a28fd5 --- /dev/null +++ b/remote/test/puppeteer/src/injected/injected.ts @@ -0,0 +1,14 @@ +import {createDeferredPromise} from '../util/DeferredPromise.js'; +import * as Poller from './Poller.js'; +import * as util from './util.js'; + +Object.assign( + self, + Object.freeze({ + InjectedUtil: { + ...Poller, + ...util, + createDeferredPromise, + }, + }) +); diff --git a/remote/test/puppeteer/src/injected/util.ts b/remote/test/puppeteer/src/injected/util.ts new file mode 100644 index 0000000000..79e68e5e0f --- /dev/null +++ b/remote/test/puppeteer/src/injected/util.ts @@ -0,0 +1,18 @@ +const createdFunctions = new Map<string, (...args: unknown[]) => unknown>(); + +/** + * Creates a function from a string. + */ +export const createFunction = ( + functionValue: string +): ((...args: unknown[]) => unknown) => { + let fn = createdFunctions.get(functionValue); + if (fn) { + return fn; + } + fn = new Function(`return ${functionValue}`)() as ( + ...args: unknown[] + ) => unknown; + createdFunctions.set(functionValue, fn); + return fn; +}; |