summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/src/injected
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/src/injected')
-rw-r--r--remote/test/puppeteer/src/injected/Poller.ts156
-rw-r--r--remote/test/puppeteer/src/injected/README.md5
-rw-r--r--remote/test/puppeteer/src/injected/injected.ts14
-rw-r--r--remote/test/puppeteer/src/injected/util.ts18
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;
+};