summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ExecutionContext.ts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /remote/test/puppeteer/packages/puppeteer-core/src/cdp/ExecutionContext.ts
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/test/puppeteer/packages/puppeteer-core/src/cdp/ExecutionContext.ts')
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/cdp/ExecutionContext.ts392
1 files changed, 392 insertions, 0 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ExecutionContext.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ExecutionContext.ts
new file mode 100644
index 0000000000..6efdf8ac76
--- /dev/null
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/ExecutionContext.ts
@@ -0,0 +1,392 @@
+/**
+ * @license
+ * Copyright 2017 Google Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type {Protocol} from 'devtools-protocol';
+
+import type {CDPSession} from '../api/CDPSession.js';
+import type {ElementHandle} from '../api/ElementHandle.js';
+import type {JSHandle} from '../api/JSHandle.js';
+import {LazyArg} from '../common/LazyArg.js';
+import {scriptInjector} from '../common/ScriptInjector.js';
+import type {EvaluateFunc, HandleFor} from '../common/types.js';
+import {
+ PuppeteerURL,
+ SOURCE_URL_REGEX,
+ getSourcePuppeteerURLIfAvailable,
+ getSourceUrlComment,
+ isString,
+} from '../common/util.js';
+import type PuppeteerUtil from '../injected/injected.js';
+import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
+import {stringifyFunction} from '../util/Function.js';
+
+import {ARIAQueryHandler} from './AriaQueryHandler.js';
+import {Binding} from './Binding.js';
+import {CdpElementHandle} from './ElementHandle.js';
+import type {IsolatedWorld} from './IsolatedWorld.js';
+import {CdpJSHandle} from './JSHandle.js';
+import {createEvaluationError, valueFromRemoteObject} from './utils.js';
+
+/**
+ * @internal
+ */
+export class ExecutionContext {
+ _client: CDPSession;
+ _world: IsolatedWorld;
+ _contextId: number;
+ _contextName?: string;
+
+ constructor(
+ client: CDPSession,
+ contextPayload: Protocol.Runtime.ExecutionContextDescription,
+ world: IsolatedWorld
+ ) {
+ this._client = client;
+ this._world = world;
+ this._contextId = contextPayload.id;
+ if (contextPayload.name) {
+ this._contextName = contextPayload.name;
+ }
+ }
+
+ #bindingsInstalled = false;
+ #puppeteerUtil?: Promise<JSHandle<PuppeteerUtil>>;
+ get puppeteerUtil(): Promise<JSHandle<PuppeteerUtil>> {
+ let promise = Promise.resolve() as Promise<unknown>;
+ if (!this.#bindingsInstalled) {
+ promise = Promise.all([
+ this.#installGlobalBinding(
+ new Binding(
+ '__ariaQuerySelector',
+ ARIAQueryHandler.queryOne as (...args: unknown[]) => unknown
+ )
+ ),
+ this.#installGlobalBinding(
+ new Binding('__ariaQuerySelectorAll', (async (
+ element: ElementHandle<Node>,
+ selector: string
+ ): Promise<JSHandle<Node[]>> => {
+ const results = ARIAQueryHandler.queryAll(element, selector);
+ return await element.realm.evaluateHandle(
+ (...elements) => {
+ return elements;
+ },
+ ...(await AsyncIterableUtil.collect(results))
+ );
+ }) as (...args: unknown[]) => unknown)
+ ),
+ ]);
+ this.#bindingsInstalled = true;
+ }
+ scriptInjector.inject(script => {
+ if (this.#puppeteerUtil) {
+ void this.#puppeteerUtil.then(handle => {
+ void handle.dispose();
+ });
+ }
+ this.#puppeteerUtil = promise.then(() => {
+ return this.evaluateHandle(script) as Promise<JSHandle<PuppeteerUtil>>;
+ });
+ }, !this.#puppeteerUtil);
+ return this.#puppeteerUtil as Promise<JSHandle<PuppeteerUtil>>;
+ }
+
+ async #installGlobalBinding(binding: Binding) {
+ try {
+ if (this._world) {
+ this._world._bindings.set(binding.name, binding);
+ await this._world._addBindingToContext(this, binding.name);
+ }
+ } catch {
+ // If the binding cannot be added, then either the browser doesn't support
+ // bindings (e.g. Firefox) or the context is broken. Either breakage is
+ // okay, so we ignore the error.
+ }
+ }
+
+ /**
+ * Evaluates the given function.
+ *
+ * @example
+ *
+ * ```ts
+ * const executionContext = await page.mainFrame().executionContext();
+ * const result = await executionContext.evaluate(() => Promise.resolve(8 * 7))* ;
+ * console.log(result); // prints "56"
+ * ```
+ *
+ * @example
+ * A string can also be passed in instead of a function:
+ *
+ * ```ts
+ * console.log(await executionContext.evaluate('1 + 2')); // prints "3"
+ * ```
+ *
+ * @example
+ * Handles can also be passed as `args`. They resolve to their referenced object:
+ *
+ * ```ts
+ * const oneHandle = await executionContext.evaluateHandle(() => 1);
+ * const twoHandle = await executionContext.evaluateHandle(() => 2);
+ * const result = await executionContext.evaluate(
+ * (a, b) => a + b,
+ * oneHandle,
+ * twoHandle
+ * );
+ * await oneHandle.dispose();
+ * await twoHandle.dispose();
+ * console.log(result); // prints '3'.
+ * ```
+ *
+ * @param pageFunction - The function to evaluate.
+ * @param args - Additional arguments to pass into the function.
+ * @returns The result of evaluating the function. If the result is an object,
+ * a vanilla object containing the serializable properties of the result is
+ * returned.
+ */
+ async evaluate<
+ Params extends unknown[],
+ Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
+ >(
+ pageFunction: Func | string,
+ ...args: Params
+ ): Promise<Awaited<ReturnType<Func>>> {
+ return await this.#evaluate(true, pageFunction, ...args);
+ }
+
+ /**
+ * Evaluates the given function.
+ *
+ * Unlike {@link ExecutionContext.evaluate | evaluate}, this method returns a
+ * handle to the result of the function.
+ *
+ * This method may be better suited if the object cannot be serialized (e.g.
+ * `Map`) and requires further manipulation.
+ *
+ * @example
+ *
+ * ```ts
+ * const context = await page.mainFrame().executionContext();
+ * const handle: JSHandle<typeof globalThis> = await context.evaluateHandle(
+ * () => Promise.resolve(self)
+ * );
+ * ```
+ *
+ * @example
+ * A string can also be passed in instead of a function.
+ *
+ * ```ts
+ * const handle: JSHandle<number> = await context.evaluateHandle('1 + 2');
+ * ```
+ *
+ * @example
+ * Handles can also be passed as `args`. They resolve to their referenced object:
+ *
+ * ```ts
+ * const bodyHandle: ElementHandle<HTMLBodyElement> =
+ * await context.evaluateHandle(() => {
+ * return document.body;
+ * });
+ * const stringHandle: JSHandle<string> = await context.evaluateHandle(
+ * body => body.innerHTML,
+ * body
+ * );
+ * console.log(await stringHandle.jsonValue()); // prints body's innerHTML
+ * // Always dispose your garbage! :)
+ * await bodyHandle.dispose();
+ * await stringHandle.dispose();
+ * ```
+ *
+ * @param pageFunction - The function to evaluate.
+ * @param args - Additional arguments to pass into the function.
+ * @returns A {@link JSHandle | handle} to the result of evaluating the
+ * function. If the result is a `Node`, then this will return an
+ * {@link ElementHandle | element handle}.
+ */
+ async evaluateHandle<
+ Params extends unknown[],
+ Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
+ >(
+ pageFunction: Func | string,
+ ...args: Params
+ ): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
+ return await this.#evaluate(false, pageFunction, ...args);
+ }
+
+ async #evaluate<
+ Params extends unknown[],
+ Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
+ >(
+ returnByValue: true,
+ pageFunction: Func | string,
+ ...args: Params
+ ): Promise<Awaited<ReturnType<Func>>>;
+ async #evaluate<
+ Params extends unknown[],
+ Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
+ >(
+ returnByValue: false,
+ pageFunction: Func | string,
+ ...args: Params
+ ): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
+ async #evaluate<
+ Params extends unknown[],
+ Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
+ >(
+ returnByValue: boolean,
+ pageFunction: Func | string,
+ ...args: Params
+ ): Promise<HandleFor<Awaited<ReturnType<Func>>> | Awaited<ReturnType<Func>>> {
+ const sourceUrlComment = getSourceUrlComment(
+ getSourcePuppeteerURLIfAvailable(pageFunction)?.toString() ??
+ PuppeteerURL.INTERNAL_URL
+ );
+
+ if (isString(pageFunction)) {
+ const contextId = this._contextId;
+ const expression = pageFunction;
+ const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression)
+ ? expression
+ : `${expression}\n${sourceUrlComment}\n`;
+
+ const {exceptionDetails, result: remoteObject} = await this._client
+ .send('Runtime.evaluate', {
+ expression: expressionWithSourceUrl,
+ contextId,
+ returnByValue,
+ awaitPromise: true,
+ userGesture: true,
+ })
+ .catch(rewriteError);
+
+ if (exceptionDetails) {
+ throw createEvaluationError(exceptionDetails);
+ }
+
+ return returnByValue
+ ? valueFromRemoteObject(remoteObject)
+ : createCdpHandle(this._world, remoteObject);
+ }
+
+ const functionDeclaration = stringifyFunction(pageFunction);
+ const functionDeclarationWithSourceUrl = SOURCE_URL_REGEX.test(
+ functionDeclaration
+ )
+ ? functionDeclaration
+ : `${functionDeclaration}\n${sourceUrlComment}\n`;
+ let callFunctionOnPromise;
+ try {
+ callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
+ functionDeclaration: functionDeclarationWithSourceUrl,
+ executionContextId: this._contextId,
+ arguments: args.length
+ ? await Promise.all(args.map(convertArgument.bind(this)))
+ : [],
+ returnByValue,
+ awaitPromise: true,
+ userGesture: true,
+ });
+ } catch (error) {
+ if (
+ error instanceof TypeError &&
+ error.message.startsWith('Converting circular structure to JSON')
+ ) {
+ error.message += ' Recursive objects are not allowed.';
+ }
+ throw error;
+ }
+ const {exceptionDetails, result: remoteObject} =
+ await callFunctionOnPromise.catch(rewriteError);
+ if (exceptionDetails) {
+ throw createEvaluationError(exceptionDetails);
+ }
+ return returnByValue
+ ? valueFromRemoteObject(remoteObject)
+ : createCdpHandle(this._world, remoteObject);
+
+ async function convertArgument(
+ this: ExecutionContext,
+ arg: unknown
+ ): Promise<Protocol.Runtime.CallArgument> {
+ if (arg instanceof LazyArg) {
+ arg = await arg.get(this);
+ }
+ if (typeof arg === 'bigint') {
+ // eslint-disable-line valid-typeof
+ return {unserializableValue: `${arg.toString()}n`};
+ }
+ if (Object.is(arg, -0)) {
+ return {unserializableValue: '-0'};
+ }
+ if (Object.is(arg, Infinity)) {
+ return {unserializableValue: 'Infinity'};
+ }
+ if (Object.is(arg, -Infinity)) {
+ return {unserializableValue: '-Infinity'};
+ }
+ if (Object.is(arg, NaN)) {
+ return {unserializableValue: 'NaN'};
+ }
+ const objectHandle =
+ arg && (arg instanceof CdpJSHandle || arg instanceof CdpElementHandle)
+ ? arg
+ : null;
+ if (objectHandle) {
+ if (objectHandle.realm !== this._world) {
+ throw new Error(
+ 'JSHandles can be evaluated only in the context they were created!'
+ );
+ }
+ if (objectHandle.disposed) {
+ throw new Error('JSHandle is disposed!');
+ }
+ if (objectHandle.remoteObject().unserializableValue) {
+ return {
+ unserializableValue:
+ objectHandle.remoteObject().unserializableValue,
+ };
+ }
+ if (!objectHandle.remoteObject().objectId) {
+ return {value: objectHandle.remoteObject().value};
+ }
+ return {objectId: objectHandle.remoteObject().objectId};
+ }
+ return {value: arg};
+ }
+ }
+}
+
+const rewriteError = (error: Error): Protocol.Runtime.EvaluateResponse => {
+ if (error.message.includes('Object reference chain is too long')) {
+ return {result: {type: 'undefined'}};
+ }
+ if (error.message.includes("Object couldn't be returned by value")) {
+ return {result: {type: 'undefined'}};
+ }
+
+ if (
+ error.message.endsWith('Cannot find context with specified id') ||
+ error.message.endsWith('Inspected target navigated or closed')
+ ) {
+ throw new Error(
+ 'Execution context was destroyed, most likely because of a navigation.'
+ );
+ }
+ throw error;
+};
+
+/**
+ * @internal
+ */
+export function createCdpHandle(
+ realm: IsolatedWorld,
+ remoteObject: Protocol.Runtime.RemoteObject
+): JSHandle | ElementHandle<Node> {
+ if (remoteObject.subtype === 'node') {
+ return new CdpElementHandle(realm, remoteObject);
+ }
+ return new CdpJSHandle(realm, remoteObject);
+}