summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Realm.ts
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/packages/puppeteer-core/src/bidi/Realm.ts')
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/bidi/Realm.ts344
1 files changed, 252 insertions, 92 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Realm.ts b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Realm.ts
index 84f13bc703..1027941e2f 100644
--- a/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Realm.ts
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/bidi/Realm.ts
@@ -1,80 +1,63 @@
+/**
+ * @license
+ * Copyright 2024 Google Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
-import {EventEmitter, type EventType} from '../common/EventEmitter.js';
+import type {JSHandle} from '../api/JSHandle.js';
+import {Realm} from '../api/Realm.js';
+import {ARIAQueryHandler} from '../cdp/AriaQueryHandler.js';
+import {LazyArg} from '../common/LazyArg.js';
import {scriptInjector} from '../common/ScriptInjector.js';
+import type {TimeoutSettings} from '../common/TimeoutSettings.js';
import type {EvaluateFunc, HandleFor} from '../common/types.js';
import {
- PuppeteerURL,
- SOURCE_URL_REGEX,
+ debugError,
getSourcePuppeteerURLIfAvailable,
getSourceUrlComment,
isString,
+ PuppeteerURL,
+ SOURCE_URL_REGEX,
} from '../common/util.js';
import type PuppeteerUtil from '../injected/injected.js';
-import {disposeSymbol} from '../util/disposable.js';
+import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {stringifyFunction} from '../util/Function.js';
-import type {BidiConnection} from './Connection.js';
+import type {
+ Realm as BidiRealmCore,
+ DedicatedWorkerRealm,
+ SharedWorkerRealm,
+} from './core/Realm.js';
+import type {WindowRealm} from './core/Realm.js';
import {BidiDeserializer} from './Deserializer.js';
import {BidiElementHandle} from './ElementHandle.js';
+import {ExposeableFunction} from './ExposedFunction.js';
+import type {BidiFrame} from './Frame.js';
import {BidiJSHandle} from './JSHandle.js';
-import type {Sandbox} from './Sandbox.js';
import {BidiSerializer} from './Serializer.js';
import {createEvaluationError} from './util.js';
+import type {BidiWebWorker} from './WebWorker.js';
/**
* @internal
*/
-export class BidiRealm extends EventEmitter<Record<EventType, any>> {
- readonly connection: BidiConnection;
-
- #id!: string;
- #sandbox!: Sandbox;
+export abstract class BidiRealm extends Realm {
+ readonly realm: BidiRealmCore;
- constructor(connection: BidiConnection) {
- super();
- this.connection = connection;
+ constructor(realm: BidiRealmCore, timeoutSettings: TimeoutSettings) {
+ super(timeoutSettings);
+ this.realm = realm;
}
- get target(): Bidi.Script.Target {
- return {
- context: this.#sandbox.environment._id,
- sandbox: this.#sandbox.name,
- };
- }
-
- handleRealmDestroyed = async (
- params: Bidi.Script.RealmDestroyed['params']
- ): Promise<void> => {
- if (params.realm === this.#id) {
- // Note: The Realm is destroyed, so in theory the handle should be as
- // well.
+ protected initialize(): void {
+ this.realm.on('destroyed', ({reason}) => {
+ this.taskManager.terminateAll(new Error(reason));
+ });
+ this.realm.on('updated', () => {
this.internalPuppeteerUtil = undefined;
- this.#sandbox.environment.clearDocumentHandle();
- }
- };
-
- handleRealmCreated = (params: Bidi.Script.RealmCreated['params']): void => {
- if (
- params.type === 'window' &&
- params.context === this.#sandbox.environment._id &&
- params.sandbox === this.#sandbox.name
- ) {
- this.#id = params.realm;
- void this.#sandbox.taskManager.rerunAll();
- }
- };
-
- setSandbox(sandbox: Sandbox): void {
- this.#sandbox = sandbox;
- this.connection.on(
- Bidi.ChromiumBidi.Script.EventNames.RealmCreated,
- this.handleRealmCreated
- );
- this.connection.on(
- Bidi.ChromiumBidi.Script.EventNames.RealmDestroyed,
- this.handleRealmDestroyed
- );
+ void this.taskManager.rerunAll();
+ });
}
protected internalPuppeteerUtil?: Promise<BidiJSHandle<PuppeteerUtil>>;
@@ -95,7 +78,7 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
return this.internalPuppeteerUtil as Promise<BidiJSHandle<PuppeteerUtil>>;
}
- async evaluateHandle<
+ override async evaluateHandle<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
@@ -105,7 +88,7 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
return await this.#evaluate(false, pageFunction, ...args);
}
- async evaluate<
+ override async evaluate<
Params extends unknown[],
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
>(
@@ -144,8 +127,6 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
PuppeteerURL.INTERNAL_URL
);
- const sandbox = this.#sandbox;
-
let responsePromise;
const resultOwnership = returnByValue
? Bidi.Script.ResultOwnership.None
@@ -161,11 +142,8 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
? pageFunction
: `${pageFunction}\n${sourceUrlComment}\n`;
- responsePromise = this.connection.send('script.evaluate', {
- expression,
- target: this.target,
+ responsePromise = this.realm.evaluate(expression, true, {
resultOwnership,
- awaitPromise: true,
userActivation: true,
serializationOptions,
});
@@ -174,24 +152,25 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration)
? functionDeclaration
: `${functionDeclaration}\n${sourceUrlComment}\n`;
- responsePromise = this.connection.send('script.callFunction', {
+ responsePromise = this.realm.callFunction(
functionDeclaration,
- arguments: args.length
- ? await Promise.all(
- args.map(arg => {
- return BidiSerializer.serialize(sandbox, arg);
- })
- )
- : [],
- target: this.target,
- resultOwnership,
- awaitPromise: true,
- userActivation: true,
- serializationOptions,
- });
+ /* awaitPromise= */ true,
+ {
+ arguments: args.length
+ ? await Promise.all(
+ args.map(arg => {
+ return this.serialize(arg);
+ })
+ )
+ : [],
+ resultOwnership,
+ userActivation: true,
+ serializationOptions,
+ }
+ );
}
- const {result} = await responsePromise;
+ const result = await responsePromise;
if ('type' in result && result.type === 'exception') {
throw createEvaluationError(result.exceptionDetails);
@@ -199,30 +178,211 @@ export class BidiRealm extends EventEmitter<Record<EventType, any>> {
return returnByValue
? BidiDeserializer.deserialize(result.result)
- : createBidiHandle(sandbox, result.result);
+ : this.createHandle(result.result);
}
- [disposeSymbol](): void {
- this.connection.off(
- Bidi.ChromiumBidi.Script.EventNames.RealmCreated,
- this.handleRealmCreated
- );
- this.connection.off(
- Bidi.ChromiumBidi.Script.EventNames.RealmDestroyed,
- this.handleRealmDestroyed
+ createHandle(
+ result: Bidi.Script.RemoteValue
+ ): BidiJSHandle<unknown> | BidiElementHandle<Node> {
+ if (
+ (result.type === 'node' || result.type === 'window') &&
+ this instanceof BidiFrameRealm
+ ) {
+ return BidiElementHandle.from(result, this);
+ }
+ return BidiJSHandle.from(result, this);
+ }
+
+ async serialize(arg: unknown): Promise<Bidi.Script.LocalValue> {
+ if (arg instanceof LazyArg) {
+ arg = await arg.get(this);
+ }
+
+ if (arg instanceof BidiJSHandle || arg instanceof BidiElementHandle) {
+ if (arg.realm !== this) {
+ if (
+ !(arg.realm instanceof BidiFrameRealm) ||
+ !(this instanceof BidiFrameRealm)
+ ) {
+ throw new Error(
+ "Trying to evaluate JSHandle from different global types. Usually this means you're using a handle from a worker in a page or vice versa."
+ );
+ }
+ if (arg.realm.environment !== this.environment) {
+ throw new Error(
+ "Trying to evaluate JSHandle from different frames. Usually this means you're using a handle from a page on a different page."
+ );
+ }
+ }
+ if (arg.disposed) {
+ throw new Error('JSHandle is disposed!');
+ }
+ return arg.remoteValue() as Bidi.Script.RemoteReference;
+ }
+
+ return BidiSerializer.serialize(arg);
+ }
+
+ async destroyHandles(handles: Array<BidiJSHandle<unknown>>): Promise<void> {
+ const handleIds = handles
+ .map(({id}) => {
+ return id;
+ })
+ .filter((id): id is string => {
+ return id !== undefined;
+ });
+
+ if (handleIds.length === 0) {
+ return;
+ }
+
+ await this.realm.disown(handleIds).catch(error => {
+ // Exceptions might happen in case of a page been navigated or closed.
+ // Swallow these since they are harmless and we don't leak anything in this case.
+ debugError(error);
+ });
+ }
+
+ override async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
+ return (await this.evaluateHandle(node => {
+ return node;
+ }, handle)) as unknown as T;
+ }
+
+ override async transferHandle<T extends JSHandle<Node>>(
+ handle: T
+ ): Promise<T> {
+ if (handle.realm === this) {
+ return handle;
+ }
+ const transferredHandle = this.adoptHandle(handle);
+ await handle.dispose();
+ return await transferredHandle;
+ }
+}
+
+/**
+ * @internal
+ */
+export class BidiFrameRealm extends BidiRealm {
+ static from(realm: WindowRealm, frame: BidiFrame): BidiFrameRealm {
+ const frameRealm = new BidiFrameRealm(realm, frame);
+ frameRealm.#initialize();
+ return frameRealm;
+ }
+ declare readonly realm: WindowRealm;
+
+ readonly #frame: BidiFrame;
+
+ private constructor(realm: WindowRealm, frame: BidiFrame) {
+ super(realm, frame.timeoutSettings);
+ this.#frame = frame;
+ }
+
+ #initialize() {
+ super.initialize();
+
+ // This should run first.
+ this.realm.on('updated', () => {
+ this.environment.clearDocumentHandle();
+ this.#bindingsInstalled = false;
+ });
+ }
+
+ #bindingsInstalled = false;
+ override get puppeteerUtil(): Promise<BidiJSHandle<PuppeteerUtil>> {
+ let promise = Promise.resolve() as Promise<unknown>;
+ if (!this.#bindingsInstalled) {
+ promise = Promise.all([
+ ExposeableFunction.from(
+ this.environment as BidiFrame,
+ '__ariaQuerySelector',
+ ARIAQueryHandler.queryOne,
+ !!this.sandbox
+ ),
+ ExposeableFunction.from(
+ this.environment as BidiFrame,
+ '__ariaQuerySelectorAll',
+ async (
+ element: BidiElementHandle<Node>,
+ selector: string
+ ): Promise<JSHandle<Node[]>> => {
+ const results = ARIAQueryHandler.queryAll(element, selector);
+ return await element.realm.evaluateHandle(
+ (...elements) => {
+ return elements;
+ },
+ ...(await AsyncIterableUtil.collect(results))
+ );
+ },
+ !!this.sandbox
+ ),
+ ]);
+ this.#bindingsInstalled = true;
+ }
+ return promise.then(() => {
+ return super.puppeteerUtil;
+ });
+ }
+
+ get sandbox(): string | undefined {
+ return this.realm.sandbox;
+ }
+
+ override get environment(): BidiFrame {
+ return this.#frame;
+ }
+
+ override async adoptBackendNode(
+ backendNodeId?: number | undefined
+ ): Promise<JSHandle<Node>> {
+ const {object} = await this.#frame.client.send('DOM.resolveNode', {
+ backendNodeId,
+ executionContextId: await this.realm.resolveExecutionContextId(),
+ });
+ using handle = BidiElementHandle.from(
+ {
+ handle: object.objectId,
+ type: 'node',
+ },
+ this
);
+ // We need the sharedId, so we perform the following to obtain it.
+ return await handle.evaluateHandle(element => {
+ return element;
+ });
}
}
/**
* @internal
*/
-export function createBidiHandle(
- sandbox: Sandbox,
- result: Bidi.Script.RemoteValue
-): BidiJSHandle<unknown> | BidiElementHandle<Node> {
- if (result.type === 'node' || result.type === 'window') {
- return new BidiElementHandle(sandbox, result);
- }
- return new BidiJSHandle(sandbox, result);
+export class BidiWorkerRealm extends BidiRealm {
+ static from(
+ realm: DedicatedWorkerRealm | SharedWorkerRealm,
+ worker: BidiWebWorker
+ ): BidiWorkerRealm {
+ const workerRealm = new BidiWorkerRealm(realm, worker);
+ workerRealm.initialize();
+ return workerRealm;
+ }
+ declare readonly realm: DedicatedWorkerRealm | SharedWorkerRealm;
+
+ readonly #worker: BidiWebWorker;
+
+ private constructor(
+ realm: DedicatedWorkerRealm | SharedWorkerRealm,
+ frame: BidiWebWorker
+ ) {
+ super(realm, frame.timeoutSettings);
+ this.#worker = frame;
+ }
+
+ override get environment(): BidiWebWorker {
+ return this.#worker;
+ }
+
+ override async adoptBackendNode(): Promise<JSHandle<Node>> {
+ throw new Error('Cannot adopt DOM nodes into a worker.');
+ }
}