mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: refactor handles to use realms (#10830)
This commit is contained in:
parent
7e74439c51
commit
f3c7499e67
@ -10,7 +10,7 @@ Evaluates the given function with the current handle as its first argument.
|
||||
|
||||
```typescript
|
||||
class JSHandle {
|
||||
abstract evaluate<
|
||||
evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
|
@ -10,7 +10,7 @@ Evaluates the given function with the current handle as its first argument.
|
||||
|
||||
```typescript
|
||||
class JSHandle {
|
||||
abstract evaluateHandle<
|
||||
evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
|
@ -10,13 +10,13 @@ Gets a map of handles representing the properties of the current handle.
|
||||
|
||||
```typescript
|
||||
class JSHandle {
|
||||
abstract getProperties(): Promise<Map<string, JSHandle<unknown>>>;
|
||||
getProperties(): Promise<Map<string, JSHandle>>;
|
||||
}
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
|
||||
Promise<Map<string, [JSHandle](./puppeteer.jshandle.md)<unknown>>>
|
||||
Promise<Map<string, [JSHandle](./puppeteer.jshandle.md)>>
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -10,7 +10,7 @@ Fetches a single property from the referenced object.
|
||||
|
||||
```typescript
|
||||
class JSHandle {
|
||||
abstract getProperty<K extends keyof T>(
|
||||
getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>>;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ sidebar_label: JSHandle.getProperty_1
|
||||
|
||||
```typescript
|
||||
class JSHandle {
|
||||
abstract getProperty(propertyName: string): Promise<JSHandle<unknown>>;
|
||||
getProperty(propertyName: string): Promise<JSHandle<unknown>>;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -142,7 +142,7 @@ export abstract class ElementHandle<
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected handle;
|
||||
protected readonly handle;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -208,7 +208,7 @@ export abstract class ElementHandle<
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
override evaluateHandle<
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<ElementType, Params> = EvaluateFuncWith<
|
||||
ElementType,
|
||||
@ -218,7 +218,7 @@ export abstract class ElementHandle<
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return this.handle.evaluateHandle(pageFunction, ...args);
|
||||
return await this.handle.evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1269,11 +1269,6 @@ export abstract class ElementHandle<
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract assertElementHasWorld(): asserts this;
|
||||
|
||||
/**
|
||||
* If the element is a form input, you can use {@link ElementHandle.autofill}
|
||||
* to test if the form is compatible with the browser's autofill
|
||||
|
27
packages/puppeteer-core/src/api/Environment.ts
Normal file
27
packages/puppeteer-core/src/api/Environment.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {CDPSession} from '../common/Connection.js';
|
||||
|
||||
import {Realm} from './Realm.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface Environment {
|
||||
get client(): CDPSession;
|
||||
mainRealm(): Realm;
|
||||
}
|
@ -20,7 +20,6 @@ import {Page, WaitTimeoutOptions} from '../api/Page.js';
|
||||
import {CDPSession} from '../common/Connection.js';
|
||||
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
||||
import {EventEmitter} from '../common/EventEmitter.js';
|
||||
import {ExecutionContext} from '../common/ExecutionContext.js';
|
||||
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
|
||||
import {transposeIterableHandle} from '../common/HandleIterator.js';
|
||||
import {
|
||||
@ -309,14 +308,7 @@ export abstract class Frame extends EventEmitter {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract _client(): CDPSession;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
executionContext(): Promise<ExecutionContext> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
abstract get client(): CDPSession;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -475,7 +467,7 @@ export abstract class Frame extends EventEmitter {
|
||||
* @returns A promise to the result of the function.
|
||||
*/
|
||||
@throwIfDetached
|
||||
$eval<
|
||||
async $eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<NodeFor<Selector>, Params> = EvaluateFuncWith<
|
||||
@ -488,7 +480,7 @@ export abstract class Frame extends EventEmitter {
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction);
|
||||
return this.mainRealm().$eval(selector, pageFunction, ...args);
|
||||
return await this.mainRealm().$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -512,7 +504,7 @@ export abstract class Frame extends EventEmitter {
|
||||
* @returns A promise to the result of the function.
|
||||
*/
|
||||
@throwIfDetached
|
||||
$$eval<
|
||||
async $$eval<
|
||||
Selector extends string,
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<
|
||||
@ -525,7 +517,7 @@ export abstract class Frame extends EventEmitter {
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction);
|
||||
return this.mainRealm().$$eval(selector, pageFunction, ...args);
|
||||
return await this.mainRealm().$$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -539,8 +531,8 @@ export abstract class Frame extends EventEmitter {
|
||||
* @param expression - the XPath expression to evaluate.
|
||||
*/
|
||||
@throwIfDetached
|
||||
$x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
||||
return this.mainRealm().$x(expression);
|
||||
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
||||
return await this.mainRealm().$x(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -659,7 +651,7 @@ export abstract class Frame extends EventEmitter {
|
||||
* @returns the promise which resolve when the `pageFunction` returns a truthy value.
|
||||
*/
|
||||
@throwIfDetached
|
||||
waitForFunction<
|
||||
async waitForFunction<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
||||
>(
|
||||
@ -667,18 +659,18 @@ export abstract class Frame extends EventEmitter {
|
||||
options: FrameWaitForFunctionOptions = {},
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return this.mainRealm().waitForFunction(
|
||||
return await (this.mainRealm().waitForFunction(
|
||||
pageFunction,
|
||||
options,
|
||||
...args
|
||||
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||
) as Promise<HandleFor<Awaited<ReturnType<Func>>>>);
|
||||
}
|
||||
/**
|
||||
* The full HTML contents of the frame, including the DOCTYPE.
|
||||
*/
|
||||
@throwIfDetached
|
||||
content(): Promise<string> {
|
||||
return this.isolatedRealm().content();
|
||||
async content(): Promise<string> {
|
||||
return await this.isolatedRealm().content();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -922,8 +914,11 @@ export abstract class Frame extends EventEmitter {
|
||||
* @param selector - The selector to query for.
|
||||
*/
|
||||
@throwIfDetached
|
||||
click(selector: string, options: Readonly<ClickOptions> = {}): Promise<void> {
|
||||
return this.isolatedRealm().click(selector, options);
|
||||
async click(
|
||||
selector: string,
|
||||
options: Readonly<ClickOptions> = {}
|
||||
): Promise<void> {
|
||||
return await this.isolatedRealm().click(selector, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -933,8 +928,8 @@ export abstract class Frame extends EventEmitter {
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
@throwIfDetached
|
||||
focus(selector: string): Promise<void> {
|
||||
return this.isolatedRealm().focus(selector);
|
||||
async focus(selector: string): Promise<void> {
|
||||
return await this.isolatedRealm().focus(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -945,8 +940,8 @@ export abstract class Frame extends EventEmitter {
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
@throwIfDetached
|
||||
hover(selector: string): Promise<void> {
|
||||
return this.isolatedRealm().hover(selector);
|
||||
async hover(selector: string): Promise<void> {
|
||||
return await this.isolatedRealm().hover(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -968,8 +963,8 @@ export abstract class Frame extends EventEmitter {
|
||||
* @throws Throws if there's no `<select>` matching `selector`.
|
||||
*/
|
||||
@throwIfDetached
|
||||
select(selector: string, ...values: string[]): Promise<string[]> {
|
||||
return this.isolatedRealm().select(selector, ...values);
|
||||
async select(selector: string, ...values: string[]): Promise<string[]> {
|
||||
return await this.isolatedRealm().select(selector, ...values);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -979,8 +974,8 @@ export abstract class Frame extends EventEmitter {
|
||||
* @throws Throws if there's no element matching `selector`.
|
||||
*/
|
||||
@throwIfDetached
|
||||
tap(selector: string): Promise<void> {
|
||||
return this.isolatedRealm().tap(selector);
|
||||
async tap(selector: string): Promise<void> {
|
||||
return await this.isolatedRealm().tap(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1005,12 +1000,12 @@ export abstract class Frame extends EventEmitter {
|
||||
* between key presses in milliseconds. Defaults to `0`.
|
||||
*/
|
||||
@throwIfDetached
|
||||
type(
|
||||
async type(
|
||||
selector: string,
|
||||
text: string,
|
||||
options?: Readonly<KeyboardTypeOptions>
|
||||
): Promise<void> {
|
||||
return this.isolatedRealm().type(selector, text, options);
|
||||
return await this.isolatedRealm().type(selector, text, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1033,8 +1028,8 @@ export abstract class Frame extends EventEmitter {
|
||||
*
|
||||
* @param milliseconds - the number of milliseconds to wait.
|
||||
*/
|
||||
waitForTimeout(milliseconds: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
async waitForTimeout(milliseconds: number): Promise<void> {
|
||||
return await new Promise(resolve => {
|
||||
setTimeout(resolve, milliseconds);
|
||||
});
|
||||
}
|
||||
@ -1043,8 +1038,8 @@ export abstract class Frame extends EventEmitter {
|
||||
* The frame's title.
|
||||
*/
|
||||
@throwIfDetached
|
||||
title(): Promise<string> {
|
||||
return this.isolatedRealm().title();
|
||||
async title(): Promise<string> {
|
||||
return await this.isolatedRealm().title();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,10 +23,11 @@ import {
|
||||
HandleOr,
|
||||
Moveable,
|
||||
} from '../common/types.js';
|
||||
import {debugError} from '../common/util.js';
|
||||
import {debugError, withSourcePuppeteerURLIfNone} from '../common/util.js';
|
||||
import {moveable} from '../util/decorators.js';
|
||||
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {Realm} from './Realm.js';
|
||||
|
||||
/**
|
||||
* Represents a reference to a JavaScript object. Instances can be created using
|
||||
@ -65,6 +66,11 @@ export abstract class JSHandle<T = unknown>
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract get realm(): Realm;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -75,33 +81,56 @@ export abstract class JSHandle<T = unknown>
|
||||
/**
|
||||
* Evaluates the given function with the current handle as its first argument.
|
||||
*/
|
||||
abstract evaluate<
|
||||
async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.realm.evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given function with the current handle as its first argument.
|
||||
*
|
||||
*/
|
||||
abstract evaluateHandle<
|
||||
async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.realm.evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a single property from the referenced object.
|
||||
*/
|
||||
abstract getProperty<K extends keyof T>(
|
||||
getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>>;
|
||||
abstract getProperty(propertyName: string): Promise<JSHandle<unknown>>;
|
||||
getProperty(propertyName: string): Promise<JSHandle<unknown>>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>> {
|
||||
return await this.evaluateHandle((object, propertyName) => {
|
||||
return object[propertyName as K];
|
||||
}, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a map of handles representing the properties of the current handle.
|
||||
@ -121,7 +150,31 @@ export abstract class JSHandle<T = unknown>
|
||||
* children; // holds elementHandles to all children of document.body
|
||||
* ```
|
||||
*/
|
||||
abstract getProperties(): Promise<Map<string, JSHandle<unknown>>>;
|
||||
async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
const propertyNames = await this.evaluate(object => {
|
||||
const enumerableProperties = [];
|
||||
const descriptors = Object.getOwnPropertyDescriptors(object);
|
||||
for (const propertyName in descriptors) {
|
||||
if (descriptors[propertyName]?.enumerable) {
|
||||
enumerableProperties.push(propertyName);
|
||||
}
|
||||
}
|
||||
return enumerableProperties;
|
||||
});
|
||||
const map = new Map<string, JSHandle>();
|
||||
const results = await Promise.all(
|
||||
propertyNames.map(key => {
|
||||
return this.getProperty(key);
|
||||
})
|
||||
);
|
||||
for (const [key, value] of Object.entries(propertyNames)) {
|
||||
using handle = results[key as any];
|
||||
if (handle) {
|
||||
map.set(value, handle.move());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* A vanilla object representing the serializable portions of the
|
||||
|
@ -27,6 +27,7 @@ import {TaskManager, WaitTask} from '../common/WaitTask.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
import {ClickOptions, ElementHandle} from './ElementHandle.js';
|
||||
import {Environment} from './Environment.js';
|
||||
import {KeyboardTypeOptions} from './Input.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
|
||||
@ -34,20 +35,14 @@ import {JSHandle} from './JSHandle.js';
|
||||
* @internal
|
||||
*/
|
||||
export abstract class Realm implements Disposable {
|
||||
#timeoutSettings: TimeoutSettings;
|
||||
#taskManager = new TaskManager();
|
||||
protected readonly timeoutSettings: TimeoutSettings;
|
||||
readonly taskManager = new TaskManager();
|
||||
|
||||
constructor(timeoutSettings: TimeoutSettings) {
|
||||
this.#timeoutSettings = timeoutSettings;
|
||||
this.timeoutSettings = timeoutSettings;
|
||||
}
|
||||
|
||||
protected get timeoutSettings(): TimeoutSettings {
|
||||
return this.#timeoutSettings;
|
||||
}
|
||||
|
||||
get taskManager(): TaskManager {
|
||||
return this.#taskManager;
|
||||
}
|
||||
abstract get environment(): Environment;
|
||||
|
||||
abstract adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T>;
|
||||
abstract transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T>;
|
||||
@ -136,7 +131,7 @@ export abstract class Realm implements Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
waitForFunction<
|
||||
async waitForFunction<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<InnerLazyParams<Params>> = EvaluateFunc<
|
||||
InnerLazyParams<Params>
|
||||
@ -153,7 +148,7 @@ export abstract class Realm implements Disposable {
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
const {
|
||||
polling = 'raf',
|
||||
timeout = this.#timeoutSettings.timeout(),
|
||||
timeout = this.timeoutSettings.timeout(),
|
||||
root,
|
||||
signal,
|
||||
} = options;
|
||||
@ -173,7 +168,7 @@ export abstract class Realm implements Disposable {
|
||||
| string,
|
||||
...args
|
||||
);
|
||||
return waitTask.result;
|
||||
return await waitTask.result;
|
||||
}
|
||||
|
||||
async click(
|
||||
|
@ -21,7 +21,7 @@ import {assert} from '../util/assert.js';
|
||||
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {QueryHandler, QuerySelector} from './QueryHandler.js';
|
||||
import {AwaitableIterable} from './types.js';
|
||||
|
||||
@ -106,16 +106,17 @@ export class ARIAQueryHandler extends QueryHandler {
|
||||
element: ElementHandle<Node>,
|
||||
selector: string
|
||||
): AwaitableIterable<ElementHandle<Node>> {
|
||||
const context = (
|
||||
element as unknown as CDPJSHandle<Node>
|
||||
).executionContext();
|
||||
const {name, role} = parseARIASelector(selector);
|
||||
const results = await queryAXTree(context._client, element, name, role);
|
||||
const world = context._world!;
|
||||
const results = await queryAXTree(
|
||||
element.realm.environment.client,
|
||||
element,
|
||||
name,
|
||||
role
|
||||
);
|
||||
yield* AsyncIterableUtil.map(results, node => {
|
||||
return world.adoptBackendNode(node.backendDOMNodeId) as Promise<
|
||||
ElementHandle<Node>
|
||||
>;
|
||||
return (element.realm as IsolatedWorld).adoptBackendNode(
|
||||
node.backendDOMNodeId
|
||||
) as Promise<ElementHandle<Node>>;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,10 @@ import {assert} from '../util/assert.js';
|
||||
import {throwIfDisposed} from '../util/decorators.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {CDPFrame} from './Frame.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {NodeFor} from './types.js';
|
||||
import {debugError} from './util.js';
|
||||
|
||||
/**
|
||||
@ -40,20 +38,17 @@ import {debugError} from './util.js';
|
||||
export class CDPElementHandle<
|
||||
ElementType extends Node = Element,
|
||||
> extends ElementHandle<ElementType> {
|
||||
#frame: CDPFrame;
|
||||
declare handle: CDPJSHandle<ElementType>;
|
||||
protected declare readonly handle: CDPJSHandle<ElementType>;
|
||||
|
||||
constructor(
|
||||
context: ExecutionContext,
|
||||
remoteObject: Protocol.Runtime.RemoteObject,
|
||||
frame: CDPFrame
|
||||
world: IsolatedWorld,
|
||||
remoteObject: Protocol.Runtime.RemoteObject
|
||||
) {
|
||||
super(new CDPJSHandle(context, remoteObject));
|
||||
this.#frame = frame;
|
||||
super(new CDPJSHandle(world, remoteObject));
|
||||
}
|
||||
|
||||
executionContext(): ExecutionContext {
|
||||
return this.handle.executionContext();
|
||||
override get realm(): IsolatedWorld {
|
||||
return this.handle.realm;
|
||||
}
|
||||
|
||||
get client(): CDPSession {
|
||||
@ -65,48 +60,21 @@ export class CDPElementHandle<
|
||||
}
|
||||
|
||||
get #frameManager(): FrameManager {
|
||||
return this.#frame._frameManager;
|
||||
return this.frame._frameManager;
|
||||
}
|
||||
|
||||
get #page(): Page {
|
||||
return this.#frame.page();
|
||||
return this.frame.page();
|
||||
}
|
||||
|
||||
override get frame(): CDPFrame {
|
||||
return this.#frame;
|
||||
}
|
||||
|
||||
@throwIfDisposed()
|
||||
override async $<Selector extends string>(
|
||||
selector: Selector
|
||||
): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
|
||||
return await (super.$(selector) as Promise<CDPElementHandle<
|
||||
NodeFor<Selector>
|
||||
> | null>);
|
||||
}
|
||||
|
||||
@throwIfDisposed()
|
||||
override async $$<Selector extends string>(
|
||||
selector: Selector
|
||||
): Promise<Array<CDPElementHandle<NodeFor<Selector>>>> {
|
||||
return await (super.$$(selector) as Promise<
|
||||
Array<CDPElementHandle<NodeFor<Selector>>>
|
||||
>);
|
||||
}
|
||||
|
||||
@throwIfDisposed()
|
||||
override async waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options?: WaitForSelectorOptions
|
||||
): Promise<CDPElementHandle<NodeFor<Selector>> | null> {
|
||||
return (await super.waitForSelector(selector, options)) as CDPElementHandle<
|
||||
NodeFor<Selector>
|
||||
> | null;
|
||||
return this.realm.environment as CDPFrame;
|
||||
}
|
||||
|
||||
override async contentFrame(
|
||||
this: ElementHandle<HTMLIFrameElement>
|
||||
): Promise<CDPFrame>;
|
||||
|
||||
@throwIfDisposed()
|
||||
override async contentFrame(): Promise<CDPFrame | null> {
|
||||
const nodeInfo = await this.client.send('DOM.describeNode', {
|
||||
@ -320,15 +288,11 @@ export class CDPElementHandle<
|
||||
objectId: this.handle.id,
|
||||
});
|
||||
const fieldId = nodeInfo.node.backendNodeId;
|
||||
const frameId = this.#frame._id;
|
||||
const frameId = this.frame._id;
|
||||
await this.client.send('Autofill.trigger', {
|
||||
fieldId,
|
||||
frameId,
|
||||
card: data.creditCard,
|
||||
});
|
||||
}
|
||||
|
||||
override assertElementHasWorld(): asserts this {
|
||||
assert(this.executionContext()._world);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import {EvaluateFunc, HandleFor} from './types.js';
|
||||
import {
|
||||
PuppeteerURL,
|
||||
createEvaluationError,
|
||||
createJSHandle,
|
||||
createCdpHandle,
|
||||
getSourcePuppeteerURLIfAvailable,
|
||||
isString,
|
||||
valueFromRemoteObject,
|
||||
@ -70,14 +70,14 @@ const getSourceUrlComment = (url: string) => {
|
||||
*/
|
||||
export class ExecutionContext {
|
||||
_client: CDPSession;
|
||||
_world?: IsolatedWorld;
|
||||
_world: IsolatedWorld;
|
||||
_contextId: number;
|
||||
_contextName?: string;
|
||||
|
||||
constructor(
|
||||
client: CDPSession,
|
||||
contextPayload: Protocol.Runtime.ExecutionContextDescription,
|
||||
world?: IsolatedWorld
|
||||
world: IsolatedWorld
|
||||
) {
|
||||
this._client = client;
|
||||
this._world = world;
|
||||
@ -105,9 +105,7 @@ export class ExecutionContext {
|
||||
selector: string
|
||||
): Promise<JSHandle<Node[]>> => {
|
||||
const results = ARIAQueryHandler.queryAll(element, selector);
|
||||
return await (element as unknown as CDPJSHandle<Node>)
|
||||
.executionContext()
|
||||
.evaluateHandle(
|
||||
return await element.realm.evaluateHandle(
|
||||
(...elements) => {
|
||||
return elements;
|
||||
},
|
||||
@ -305,7 +303,7 @@ export class ExecutionContext {
|
||||
|
||||
return returnByValue
|
||||
? valueFromRemoteObject(remoteObject)
|
||||
: createJSHandle(this, remoteObject);
|
||||
: createCdpHandle(this._world, remoteObject);
|
||||
}
|
||||
|
||||
const functionDeclaration = stringifyFunction(pageFunction);
|
||||
@ -340,7 +338,7 @@ export class ExecutionContext {
|
||||
}
|
||||
return returnByValue
|
||||
? valueFromRemoteObject(remoteObject)
|
||||
: createJSHandle(this, remoteObject);
|
||||
: createCdpHandle(this._world, remoteObject);
|
||||
|
||||
async function convertArgument(
|
||||
this: ExecutionContext,
|
||||
@ -370,7 +368,7 @@ export class ExecutionContext {
|
||||
? arg
|
||||
: null;
|
||||
if (objectHandle) {
|
||||
if (objectHandle.executionContext() !== this) {
|
||||
if (objectHandle.realm !== this._world) {
|
||||
throw new Error(
|
||||
'JSHandles can be evaluated only in the context they were created!'
|
||||
);
|
||||
|
@ -28,7 +28,6 @@ import {
|
||||
DeviceRequestPrompt,
|
||||
DeviceRequestPromptManager,
|
||||
} from './DeviceRequestPrompt.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
@ -99,8 +98,14 @@ export class CDPFrame extends Frame {
|
||||
this.#client = client;
|
||||
if (!keepWorlds) {
|
||||
this.worlds = {
|
||||
[MAIN_WORLD]: new IsolatedWorld(this),
|
||||
[PUPPETEER_WORLD]: new IsolatedWorld(this),
|
||||
[MAIN_WORLD]: new IsolatedWorld(
|
||||
this,
|
||||
this._frameManager.timeoutSettings
|
||||
),
|
||||
[PUPPETEER_WORLD]: new IsolatedWorld(
|
||||
this,
|
||||
this._frameManager.timeoutSettings
|
||||
),
|
||||
};
|
||||
} else {
|
||||
this.worlds[MAIN_WORLD].frameUpdated();
|
||||
@ -232,14 +237,10 @@ export class CDPFrame extends Frame {
|
||||
}
|
||||
}
|
||||
|
||||
override _client(): CDPSession {
|
||||
override get client(): CDPSession {
|
||||
return this.#client;
|
||||
}
|
||||
|
||||
override executionContext(): Promise<ExecutionContext> {
|
||||
return this.worlds[MAIN_WORLD].executionContext();
|
||||
}
|
||||
|
||||
override mainRealm(): IsolatedWorld {
|
||||
return this.worlds[MAIN_WORLD];
|
||||
}
|
||||
@ -281,10 +282,12 @@ export class CDPFrame extends Frame {
|
||||
}
|
||||
|
||||
@throwIfDetached
|
||||
override waitForDevicePrompt(
|
||||
override async waitForDevicePrompt(
|
||||
options: WaitTimeoutOptions = {}
|
||||
): Promise<DeviceRequestPrompt> {
|
||||
return this.#deviceRequestPromptManager().waitForDevicePrompt(options);
|
||||
return await this.#deviceRequestPromptManager().waitForDevicePrompt(
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
_navigated(framePayload: Protocol.Page.Frame): void {
|
||||
|
@ -431,7 +431,7 @@ export class FrameManager extends EventEmitter {
|
||||
await Promise.all(
|
||||
this.frames()
|
||||
.filter(frame => {
|
||||
return frame._client() === session;
|
||||
return frame.client === session;
|
||||
})
|
||||
.map(frame => {
|
||||
// Frames might be removed before we send this, so we don't want to
|
||||
@ -489,7 +489,7 @@ export class FrameManager extends EventEmitter {
|
||||
let world: IsolatedWorld | undefined;
|
||||
if (frame) {
|
||||
// Only care about execution contexts created for the current session.
|
||||
if (frame._client() !== session) {
|
||||
if (frame.client !== session) {
|
||||
return;
|
||||
}
|
||||
if (contextPayload.auxData && contextPayload.auxData['isDefault']) {
|
||||
@ -504,8 +504,12 @@ export class FrameManager extends EventEmitter {
|
||||
world = frame.worlds[PUPPETEER_WORLD];
|
||||
}
|
||||
}
|
||||
// If there is no world, the context is not meant to be handled by us.
|
||||
if (!world) {
|
||||
return;
|
||||
}
|
||||
const context = new ExecutionContext(
|
||||
frame?._client() || this.#client,
|
||||
frame?.client || this.#client,
|
||||
contextPayload,
|
||||
world
|
||||
);
|
||||
|
@ -18,25 +18,26 @@ import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
import {Realm} from '../api/Realm.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {Deferred} from '../util/Deferred.js';
|
||||
|
||||
import {Binding} from './Binding.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {CDPFrame} from './Frame.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {BindingPayload, EvaluateFunc, HandleFor} from './types.js';
|
||||
import {
|
||||
Mutex,
|
||||
addPageBinding,
|
||||
createJSHandle,
|
||||
createCdpHandle,
|
||||
debugError,
|
||||
Mutex,
|
||||
setPageContent,
|
||||
withSourcePuppeteerURLIfNone,
|
||||
} from './util.js';
|
||||
import {WebWorker} from './WebWorker.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -91,7 +92,6 @@ export interface IsolatedWorldChart {
|
||||
* @internal
|
||||
*/
|
||||
export class IsolatedWorld extends Realm {
|
||||
#frame: CDPFrame;
|
||||
#context = Deferred.create<ExecutionContext>();
|
||||
|
||||
// Set of bindings that have been registered in the current context.
|
||||
@ -104,26 +104,32 @@ export class IsolatedWorld extends Realm {
|
||||
return this.#bindings;
|
||||
}
|
||||
|
||||
constructor(frame: CDPFrame) {
|
||||
super(frame._frameManager.timeoutSettings);
|
||||
this.#frame = frame;
|
||||
readonly #frameOrWorker: CDPFrame | WebWorker;
|
||||
|
||||
constructor(
|
||||
frameOrWorker: CDPFrame | WebWorker,
|
||||
timeoutSettings: TimeoutSettings
|
||||
) {
|
||||
super(timeoutSettings);
|
||||
this.#frameOrWorker = frameOrWorker;
|
||||
this.frameUpdated();
|
||||
}
|
||||
|
||||
get environment(): CDPFrame | WebWorker {
|
||||
return this.#frameOrWorker;
|
||||
}
|
||||
|
||||
frameUpdated(): void {
|
||||
this.#client.on('Runtime.bindingCalled', this.#onBindingCalled);
|
||||
this.client.on('Runtime.bindingCalled', this.#onBindingCalled);
|
||||
}
|
||||
|
||||
get #client(): CDPSession {
|
||||
return this.#frame._client();
|
||||
get client(): CDPSession {
|
||||
return this.#frameOrWorker.client;
|
||||
}
|
||||
|
||||
get #frameManager(): FrameManager {
|
||||
return this.#frame._frameManager;
|
||||
}
|
||||
|
||||
frame(): CDPFrame {
|
||||
return this.#frame;
|
||||
get #frame(): CDPFrame {
|
||||
assert(this.#frameOrWorker instanceof CDPFrame);
|
||||
return this.#frameOrWorker;
|
||||
}
|
||||
|
||||
clearContext(): void {
|
||||
@ -140,10 +146,10 @@ export class IsolatedWorld extends Realm {
|
||||
return this.#context.resolved();
|
||||
}
|
||||
|
||||
executionContext(): Promise<ExecutionContext> {
|
||||
#executionContext(): Promise<ExecutionContext> {
|
||||
if (this.disposed) {
|
||||
throw new Error(
|
||||
`Execution context is not available in detached frame "${this.#frame.url()}" (are you trying to evaluate?)`
|
||||
`Execution context is not available in detached frame "${this.environment.url()}" (are you trying to evaluate?)`
|
||||
);
|
||||
}
|
||||
if (this.#context === null) {
|
||||
@ -163,7 +169,7 @@ export class IsolatedWorld extends Realm {
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
const context = await this.executionContext();
|
||||
const context = await this.#executionContext();
|
||||
return await context.evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
@ -178,7 +184,7 @@ export class IsolatedWorld extends Realm {
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
const context = await this.executionContext();
|
||||
const context = await this.#executionContext();
|
||||
return await context.evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
@ -197,7 +203,7 @@ export class IsolatedWorld extends Realm {
|
||||
await setPageContent(this, html);
|
||||
|
||||
const watcher = new LifecycleWatcher(
|
||||
this.#frameManager.networkManager,
|
||||
this.#frame._frameManager.networkManager,
|
||||
this.#frame,
|
||||
waitUntil,
|
||||
timeout
|
||||
@ -296,40 +302,33 @@ export class IsolatedWorld extends Realm {
|
||||
async adoptBackendNode(
|
||||
backendNodeId?: Protocol.DOM.BackendNodeId
|
||||
): Promise<JSHandle<Node>> {
|
||||
const executionContext = await this.executionContext();
|
||||
const {object} = await this.#client.send('DOM.resolveNode', {
|
||||
const executionContext = await this.#executionContext();
|
||||
const {object} = await this.client.send('DOM.resolveNode', {
|
||||
backendNodeId: backendNodeId,
|
||||
executionContextId: executionContext._contextId,
|
||||
});
|
||||
return createJSHandle(executionContext, object) as JSHandle<Node>;
|
||||
return createCdpHandle(this, object) as JSHandle<Node>;
|
||||
}
|
||||
|
||||
async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||
const context = await this.executionContext();
|
||||
if (
|
||||
(handle as unknown as CDPJSHandle<Node>).executionContext() === context
|
||||
) {
|
||||
if (handle.realm === this) {
|
||||
// If the context has already adopted this handle, clone it so downstream
|
||||
// disposal doesn't become an issue.
|
||||
return (await handle.evaluateHandle(value => {
|
||||
return value;
|
||||
// SAFETY: We know the
|
||||
})) as unknown as T;
|
||||
}
|
||||
const nodeInfo = await this.#client.send('DOM.describeNode', {
|
||||
const nodeInfo = await this.client.send('DOM.describeNode', {
|
||||
objectId: handle.id,
|
||||
});
|
||||
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
|
||||
}
|
||||
|
||||
async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||
const context = await this.executionContext();
|
||||
if (
|
||||
(handle as unknown as CDPJSHandle<Node>).executionContext() === context
|
||||
) {
|
||||
if (handle.realm === this) {
|
||||
return handle;
|
||||
}
|
||||
const info = await this.#client.send('DOM.describeNode', {
|
||||
const info = await this.client.send('DOM.describeNode', {
|
||||
objectId: handle.remoteObject().objectId,
|
||||
});
|
||||
const newHandle = (await this.adoptBackendNode(
|
||||
@ -341,6 +340,6 @@ export class IsolatedWorld extends Realm {
|
||||
|
||||
[Symbol.dispose](): void {
|
||||
super[Symbol.dispose]();
|
||||
this.#client.off('Runtime.bindingCalled', this.#onBindingCalled);
|
||||
this.client.off('Runtime.bindingCalled', this.#onBindingCalled);
|
||||
}
|
||||
}
|
||||
|
@ -17,114 +17,39 @@
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {JSHandle} from '../api/JSHandle.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import type {CDPElementHandle} from './ElementHandle.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {EvaluateFuncWith, HandleFor, HandleOr} from './types.js';
|
||||
import {
|
||||
createJSHandle,
|
||||
releaseObject,
|
||||
valueFromRemoteObject,
|
||||
withSourcePuppeteerURLIfNone,
|
||||
} from './util.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {releaseObject, valueFromRemoteObject} from './util.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class CDPJSHandle<T = unknown> extends JSHandle<T> {
|
||||
#disposed = false;
|
||||
#context: ExecutionContext;
|
||||
#remoteObject: Protocol.Runtime.RemoteObject;
|
||||
readonly #remoteObject: Protocol.Runtime.RemoteObject;
|
||||
readonly #world: IsolatedWorld;
|
||||
|
||||
constructor(
|
||||
world: IsolatedWorld,
|
||||
remoteObject: Protocol.Runtime.RemoteObject
|
||||
) {
|
||||
super();
|
||||
this.#world = world;
|
||||
this.#remoteObject = remoteObject;
|
||||
}
|
||||
|
||||
override get disposed(): boolean {
|
||||
return this.#disposed;
|
||||
}
|
||||
|
||||
constructor(
|
||||
context: ExecutionContext,
|
||||
remoteObject: Protocol.Runtime.RemoteObject
|
||||
) {
|
||||
super();
|
||||
this.#context = context;
|
||||
this.#remoteObject = remoteObject;
|
||||
}
|
||||
|
||||
executionContext(): ExecutionContext {
|
||||
return this.#context;
|
||||
override get realm(): IsolatedWorld {
|
||||
return this.#world;
|
||||
}
|
||||
|
||||
get client(): CDPSession {
|
||||
return this.#context._client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link ExecutionContext.evaluate} for more details.
|
||||
*/
|
||||
override async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.executionContext().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link ExecutionContext.evaluateHandle} for more details.
|
||||
*/
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.executionContext().evaluateHandle(
|
||||
pageFunction,
|
||||
this,
|
||||
...args
|
||||
);
|
||||
}
|
||||
|
||||
override async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>>;
|
||||
override async getProperty(propertyName: string): Promise<JSHandle<unknown>>;
|
||||
override async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>> {
|
||||
return await this.evaluateHandle((object, propertyName) => {
|
||||
return object[propertyName as K];
|
||||
}, propertyName);
|
||||
}
|
||||
|
||||
override async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
assert(this.#remoteObject.objectId);
|
||||
// We use Runtime.getProperties rather than iterative building because the
|
||||
// iterative approach might create a distorted snapshot.
|
||||
const response = await this.client.send('Runtime.getProperties', {
|
||||
objectId: this.#remoteObject.objectId,
|
||||
ownProperties: true,
|
||||
});
|
||||
const result = new Map<string, JSHandle>();
|
||||
for (const property of response.result) {
|
||||
if (!property.enumerable || !property.value) {
|
||||
continue;
|
||||
}
|
||||
result.set(property.name, createJSHandle(this.#context, property.value));
|
||||
}
|
||||
return result;
|
||||
return this.realm.environment.client;
|
||||
}
|
||||
|
||||
override async jsonValue(): Promise<T> {
|
||||
|
@ -57,6 +57,7 @@ import {EmulationManager} from './EmulationManager.js';
|
||||
import {TargetCloseError} from './Errors.js';
|
||||
import {Handler} from './EventEmitter.js';
|
||||
import {FileChooser} from './FileChooser.js';
|
||||
import {CDPFrame} from './Frame.js';
|
||||
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js';
|
||||
import {CDPKeyboard, CDPMouse, CDPTouchscreen} from './Input.js';
|
||||
import {MAIN_WORLD} from './IsolatedWorlds.js';
|
||||
@ -74,8 +75,8 @@ import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {Tracing} from './Tracing.js';
|
||||
import {BindingPayload, EvaluateFunc, HandleFor} from './types.js';
|
||||
import {
|
||||
createCdpHandle,
|
||||
createClientError,
|
||||
createJSHandle,
|
||||
debugError,
|
||||
evaluationString,
|
||||
getReadableAsBuffer,
|
||||
@ -389,7 +390,7 @@ export class CDPPage extends Page {
|
||||
return this.#emulationManager.javascriptEnabled;
|
||||
}
|
||||
|
||||
override waitForFileChooser(
|
||||
override async waitForFileChooser(
|
||||
options: WaitTimeoutOptions = {}
|
||||
): Promise<FileChooser> {
|
||||
const needsEnable = this.#fileChooserDeferreds.size === 0;
|
||||
@ -405,14 +406,16 @@ export class CDPPage extends Page {
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
return Promise.all([deferred.valueOrThrow(), enablePromise])
|
||||
.then(([result]) => {
|
||||
try {
|
||||
const [result] = await Promise.all([
|
||||
deferred.valueOrThrow(),
|
||||
enablePromise,
|
||||
]);
|
||||
return result;
|
||||
})
|
||||
.catch(error => {
|
||||
} catch (error) {
|
||||
this.#fileChooserDeferreds.delete(deferred);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
override async setGeolocation(options: GeolocationOptions): Promise<void> {
|
||||
@ -450,7 +453,7 @@ export class CDPPage extends Page {
|
||||
}
|
||||
}
|
||||
|
||||
override mainFrame(): Frame {
|
||||
override mainFrame(): CDPFrame {
|
||||
return this.#frameManager.mainFrame();
|
||||
}
|
||||
|
||||
@ -498,14 +501,14 @@ export class CDPPage extends Page {
|
||||
return await this.#client.send('Input.setInterceptDrags', {enabled});
|
||||
}
|
||||
|
||||
override setOfflineMode(enabled: boolean): Promise<void> {
|
||||
return this.#frameManager.networkManager.setOfflineMode(enabled);
|
||||
override async setOfflineMode(enabled: boolean): Promise<void> {
|
||||
return await this.#frameManager.networkManager.setOfflineMode(enabled);
|
||||
}
|
||||
|
||||
override emulateNetworkConditions(
|
||||
override async emulateNetworkConditions(
|
||||
networkConditions: NetworkConditions | null
|
||||
): Promise<void> {
|
||||
return this.#frameManager.networkManager.emulateNetworkConditions(
|
||||
return await this.#frameManager.networkManager.emulateNetworkConditions(
|
||||
networkConditions
|
||||
);
|
||||
}
|
||||
@ -533,23 +536,27 @@ export class CDPPage extends Page {
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
const context = await this.mainFrame().executionContext();
|
||||
return await context.evaluateHandle(pageFunction, ...args);
|
||||
return await this.mainFrame().evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
override async queryObjects<Prototype>(
|
||||
prototypeHandle: JSHandle<Prototype>
|
||||
): Promise<JSHandle<Prototype[]>> {
|
||||
const context = await this.mainFrame().executionContext();
|
||||
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
|
||||
assert(
|
||||
prototypeHandle.id,
|
||||
'Prototype JSHandle must not be referencing primitive value'
|
||||
);
|
||||
const response = await context._client.send('Runtime.queryObjects', {
|
||||
const response = await this.mainFrame().client.send(
|
||||
'Runtime.queryObjects',
|
||||
{
|
||||
prototypeObjectId: prototypeHandle.id,
|
||||
});
|
||||
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
|
||||
}
|
||||
);
|
||||
return createCdpHandle(
|
||||
this.mainFrame().mainRealm(),
|
||||
response.objects
|
||||
) as HandleFor<Prototype[]>;
|
||||
}
|
||||
|
||||
override async cookies(
|
||||
@ -771,7 +778,7 @@ export class CDPPage extends Page {
|
||||
return;
|
||||
}
|
||||
const values = event.args.map(arg => {
|
||||
return createJSHandle(context, arg);
|
||||
return createCdpHandle(context._world, arg);
|
||||
});
|
||||
this.#addConsoleMessage(event.type, values, event.stackTrace);
|
||||
}
|
||||
@ -1378,10 +1385,10 @@ export class CDPPage extends Page {
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
override waitForDevicePrompt(
|
||||
override async waitForDevicePrompt(
|
||||
options: WaitTimeoutOptions = {}
|
||||
): Promise<DeviceRequestPrompt> {
|
||||
return this.mainFrame().waitForDevicePrompt(options);
|
||||
return await this.mainFrame().waitForDevicePrompt(options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,6 @@ export class QueryHandler {
|
||||
element: ElementHandle<Node>,
|
||||
selector: string
|
||||
): AwaitableIterable<ElementHandle<Node>> {
|
||||
element.assertElementHasWorld();
|
||||
using handle = await element.evaluateHandle(
|
||||
this._querySelectorAll,
|
||||
selector,
|
||||
@ -126,7 +125,6 @@ export class QueryHandler {
|
||||
element: ElementHandle<Node>,
|
||||
selector: string
|
||||
): Promise<ElementHandle<Node> | null> {
|
||||
element.assertElementHasWorld();
|
||||
using result = await element.evaluateHandle(
|
||||
this._querySelector,
|
||||
selector,
|
||||
|
@ -15,13 +15,15 @@
|
||||
*/
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
|
||||
import {Deferred} from '../util/Deferred.js';
|
||||
import {Realm} from '../api/Realm.js';
|
||||
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ConsoleMessageType} from './ConsoleMessage.js';
|
||||
import {EventEmitter} from './EventEmitter.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {EvaluateFunc, HandleFor} from './types.js';
|
||||
import {debugError, withSourcePuppeteerURLIfNone} from './util.js';
|
||||
|
||||
@ -68,7 +70,12 @@ export type ExceptionThrownCallback = (
|
||||
* @public
|
||||
*/
|
||||
export class WebWorker extends EventEmitter {
|
||||
#executionContext = Deferred.create<ExecutionContext>();
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly timeoutSettings = new TimeoutSettings();
|
||||
|
||||
#world: IsolatedWorld;
|
||||
#client: CDPSession;
|
||||
#url: string;
|
||||
|
||||
@ -84,18 +91,19 @@ export class WebWorker extends EventEmitter {
|
||||
super();
|
||||
this.#client = client;
|
||||
this.#url = url;
|
||||
this.#world = new IsolatedWorld(this, new TimeoutSettings());
|
||||
|
||||
this.#client.once('Runtime.executionContextCreated', async event => {
|
||||
const context = new ExecutionContext(client, event.context);
|
||||
this.#executionContext.resolve(context);
|
||||
this.#world.setContext(
|
||||
new ExecutionContext(client, event.context, this.#world)
|
||||
);
|
||||
});
|
||||
this.#client.on('Runtime.consoleAPICalled', async event => {
|
||||
try {
|
||||
const context = await this.#executionContext.valueOrThrow();
|
||||
return consoleAPICalled(
|
||||
event.type,
|
||||
event.args.map((object: Protocol.Runtime.RemoteObject) => {
|
||||
return new CDPJSHandle(context, object);
|
||||
return new CDPJSHandle(this.#world, object);
|
||||
}),
|
||||
event.stackTrace
|
||||
);
|
||||
@ -112,8 +120,8 @@ export class WebWorker extends EventEmitter {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async executionContext(): Promise<ExecutionContext> {
|
||||
return await this.#executionContext.valueOrThrow();
|
||||
mainRealm(): Realm {
|
||||
return this.#world;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,8 +163,7 @@ export class WebWorker extends EventEmitter {
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
const context = await this.#executionContext.valueOrThrow();
|
||||
return await context.evaluate(pageFunction, ...args);
|
||||
return await this.mainRealm().evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,7 +189,6 @@ export class WebWorker extends EventEmitter {
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
const context = await this.#executionContext.valueOrThrow();
|
||||
return await context.evaluateHandle(pageFunction, ...args);
|
||||
return await this.mainRealm().evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +148,6 @@ export class BrowsingContext extends Realm {
|
||||
browserName: string
|
||||
) {
|
||||
super(connection, info.context);
|
||||
this.connection = connection;
|
||||
this.#id = info.context;
|
||||
this.#url = info.url;
|
||||
this.#parent = info.parent;
|
||||
@ -167,8 +166,8 @@ export class BrowsingContext extends Realm {
|
||||
this.url = info.url;
|
||||
}
|
||||
|
||||
createSandboxRealm(sandbox: string): Realm {
|
||||
return new Realm(this.connection, this.#id, sandbox);
|
||||
createRealmForSandbox(): Realm {
|
||||
return new Realm(this.connection, this.#id);
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
|
@ -21,6 +21,7 @@ import {AutofillData, ElementHandle} from '../../api/ElementHandle.js';
|
||||
import {BidiFrame} from './Frame.js';
|
||||
import {BidiJSHandle} from './JSHandle.js';
|
||||
import {Realm} from './Realm.js';
|
||||
import {Sandbox} from './Sandbox.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -29,19 +30,17 @@ export class BidiElementHandle<
|
||||
ElementType extends Node = Element,
|
||||
> extends ElementHandle<ElementType> {
|
||||
declare handle: BidiJSHandle<ElementType>;
|
||||
#frame: BidiFrame;
|
||||
|
||||
constructor(
|
||||
realm: Realm,
|
||||
remoteValue: Bidi.Script.RemoteValue,
|
||||
frame: BidiFrame
|
||||
) {
|
||||
super(new BidiJSHandle(realm, remoteValue));
|
||||
this.#frame = frame;
|
||||
constructor(sandbox: Sandbox, remoteValue: Bidi.Script.RemoteValue) {
|
||||
super(new BidiJSHandle(sandbox, remoteValue));
|
||||
}
|
||||
|
||||
override get realm(): Sandbox {
|
||||
return this.handle.realm;
|
||||
}
|
||||
|
||||
override get frame(): BidiFrame {
|
||||
return this.#frame;
|
||||
return this.realm.environment;
|
||||
}
|
||||
|
||||
context(): Realm {
|
||||
@ -62,12 +61,12 @@ export class BidiElementHandle<
|
||||
}
|
||||
|
||||
override async autofill(data: AutofillData): Promise<void> {
|
||||
const client = this.#frame.context().cdpSession;
|
||||
const client = this.frame.client;
|
||||
const nodeInfo = await client.send('DOM.describeNode', {
|
||||
objectId: this.handle.id,
|
||||
});
|
||||
const fieldId = nodeInfo.node.backendNodeId;
|
||||
const frameId = this.#frame._id;
|
||||
const frameId = this.frame._id;
|
||||
await client.send('Autofill.trigger', {
|
||||
fieldId,
|
||||
frameId,
|
||||
|
@ -64,17 +64,18 @@ export class BidiFrame extends Frame {
|
||||
this._id = this.#context.id;
|
||||
this._parentId = parentId ?? undefined;
|
||||
|
||||
const puppeteerRealm = context.createSandboxRealm(UTILITY_WORLD_NAME);
|
||||
this.sandboxes = {
|
||||
[MAIN_SANDBOX]: new Sandbox(context, timeoutSettings),
|
||||
[PUPPETEER_SANDBOX]: new Sandbox(puppeteerRealm, timeoutSettings),
|
||||
[MAIN_SANDBOX]: new Sandbox(undefined, this, context, timeoutSettings),
|
||||
[PUPPETEER_SANDBOX]: new Sandbox(
|
||||
UTILITY_WORLD_NAME,
|
||||
this,
|
||||
context.createRealmForSandbox(),
|
||||
timeoutSettings
|
||||
),
|
||||
};
|
||||
|
||||
puppeteerRealm.setFrame(this);
|
||||
context.setFrame(this);
|
||||
}
|
||||
|
||||
override _client(): CDPSession {
|
||||
override get client(): CDPSession {
|
||||
return this.context().cdpSession;
|
||||
}
|
||||
|
||||
|
@ -19,102 +19,35 @@ import Protocol from 'devtools-protocol';
|
||||
|
||||
import {ElementHandle} from '../../api/ElementHandle.js';
|
||||
import {JSHandle} from '../../api/JSHandle.js';
|
||||
import {EvaluateFuncWith, HandleFor, HandleOr} from '../../common/types.js';
|
||||
import {withSourcePuppeteerURLIfNone} from '../util.js';
|
||||
|
||||
import {Realm} from './Realm.js';
|
||||
import {Sandbox} from './Sandbox.js';
|
||||
import {BidiSerializer} from './Serializer.js';
|
||||
import {releaseReference} from './utils.js';
|
||||
|
||||
export class BidiJSHandle<T = unknown> extends JSHandle<T> {
|
||||
#disposed = false;
|
||||
#realm: Realm;
|
||||
#remoteValue: Bidi.Script.RemoteValue;
|
||||
readonly #sandbox: Sandbox;
|
||||
readonly #remoteValue: Bidi.Script.RemoteValue;
|
||||
|
||||
constructor(realm: Realm, remoteValue: Bidi.Script.RemoteValue) {
|
||||
constructor(sandbox: Sandbox, remoteValue: Bidi.Script.RemoteValue) {
|
||||
super();
|
||||
this.#realm = realm;
|
||||
this.#sandbox = sandbox;
|
||||
this.#remoteValue = remoteValue;
|
||||
}
|
||||
|
||||
context(): Realm {
|
||||
return this.#realm;
|
||||
return this.realm.environment.context();
|
||||
}
|
||||
|
||||
override get realm(): Sandbox {
|
||||
return this.#sandbox;
|
||||
}
|
||||
|
||||
override get disposed(): boolean {
|
||||
return this.#disposed;
|
||||
}
|
||||
|
||||
override async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.context().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>,
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
pageFunction = withSourcePuppeteerURLIfNone(
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.context().evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
override async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>>;
|
||||
override async getProperty(propertyName: string): Promise<HandleFor<unknown>>;
|
||||
override async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>> {
|
||||
return await this.evaluateHandle((object, propertyName) => {
|
||||
return object[propertyName as K];
|
||||
}, propertyName);
|
||||
}
|
||||
|
||||
override async getProperties(): Promise<Map<string, BidiJSHandle>> {
|
||||
// TODO(lightning00blade): Either include return of depth Handles in RemoteValue
|
||||
// or new BiDi command that returns array of remote value
|
||||
const keys = await this.evaluate(object => {
|
||||
const enumerableKeys = [];
|
||||
const descriptors = Object.getOwnPropertyDescriptors(object);
|
||||
for (const key in descriptors) {
|
||||
if (descriptors[key]?.enumerable) {
|
||||
enumerableKeys.push(key);
|
||||
}
|
||||
}
|
||||
return enumerableKeys;
|
||||
});
|
||||
const map = new Map<string, BidiJSHandle>();
|
||||
const results = await Promise.all(
|
||||
keys.map(key => {
|
||||
return this.getProperty(key);
|
||||
})
|
||||
);
|
||||
|
||||
for (const [key, value] of Object.entries(keys)) {
|
||||
using handle = results[key as any];
|
||||
if (handle) {
|
||||
map.set(value, handle.move() as BidiJSHandle);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
override async jsonValue(): Promise<T> {
|
||||
return await this.evaluate(value => {
|
||||
return value;
|
||||
@ -132,7 +65,7 @@ export class BidiJSHandle<T = unknown> extends JSHandle<T> {
|
||||
this.#disposed = true;
|
||||
if ('handle' in this.#remoteValue) {
|
||||
await releaseReference(
|
||||
this.#realm,
|
||||
this.context(),
|
||||
this.#remoteValue as Bidi.Script.RemoteReference
|
||||
);
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ import {HTTPRequest} from './HTTPRequest.js';
|
||||
import {HTTPResponse} from './HTTPResponse.js';
|
||||
import {Keyboard, Mouse, Touchscreen} from './Input.js';
|
||||
import {NetworkManager} from './NetworkManager.js';
|
||||
import {getBidiHandle} from './Realm.js';
|
||||
import {createBidiHandle} from './Realm.js';
|
||||
import {BidiSerializer} from './Serializer.js';
|
||||
|
||||
/**
|
||||
@ -343,7 +343,7 @@ export class BidiPage extends Page {
|
||||
}
|
||||
if (isConsoleLogEntry(event)) {
|
||||
const args = event.args.map(arg => {
|
||||
return getBidiHandle(frame.context(), arg, frame);
|
||||
return createBidiHandle(frame.mainRealm(), arg);
|
||||
});
|
||||
|
||||
const text = args
|
||||
@ -662,12 +662,12 @@ export class BidiPage extends Page {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
override waitForRequest(
|
||||
override async waitForRequest(
|
||||
urlOrPredicate: string | ((req: HTTPRequest) => boolean | Promise<boolean>),
|
||||
options: {timeout?: number} = {}
|
||||
): Promise<HTTPRequest> {
|
||||
const {timeout = this.#timeoutSettings.timeout()} = options;
|
||||
return waitForEvent(
|
||||
return await waitForEvent(
|
||||
this.#networkManager,
|
||||
NetworkManagerEmittedEvents.Request,
|
||||
async request => {
|
||||
@ -684,14 +684,14 @@ export class BidiPage extends Page {
|
||||
);
|
||||
}
|
||||
|
||||
override waitForResponse(
|
||||
override async waitForResponse(
|
||||
urlOrPredicate:
|
||||
| string
|
||||
| ((res: HTTPResponse) => boolean | Promise<boolean>),
|
||||
options: {timeout?: number} = {}
|
||||
): Promise<HTTPResponse> {
|
||||
const {timeout = this.#timeoutSettings.timeout()} = options;
|
||||
return waitForEvent(
|
||||
return await waitForEvent(
|
||||
this.#networkManager,
|
||||
NetworkManagerEmittedEvents.Response,
|
||||
async response => {
|
||||
|
@ -14,8 +14,8 @@ import {
|
||||
|
||||
import {Connection} from './Connection.js';
|
||||
import {BidiElementHandle} from './ElementHandle.js';
|
||||
import {BidiFrame} from './Frame.js';
|
||||
import {BidiJSHandle} from './JSHandle.js';
|
||||
import {Sandbox} from './Sandbox.js';
|
||||
import {BidiSerializer} from './Serializer.js';
|
||||
import {createEvaluationError} from './utils.js';
|
||||
|
||||
@ -26,28 +26,31 @@ export const getSourceUrlComment = (url: string): string => {
|
||||
};
|
||||
|
||||
export class Realm extends EventEmitter {
|
||||
connection: Connection;
|
||||
#frame!: BidiFrame;
|
||||
#id: string;
|
||||
#sandbox?: string;
|
||||
readonly connection: Connection;
|
||||
readonly #id: string;
|
||||
|
||||
constructor(connection: Connection, id: string, sandbox?: string) {
|
||||
#sandbox!: Sandbox;
|
||||
|
||||
constructor(connection: Connection, id: string) {
|
||||
super();
|
||||
this.connection = connection;
|
||||
this.#id = id;
|
||||
this.#sandbox = sandbox;
|
||||
}
|
||||
|
||||
get target(): Bidi.Script.Target {
|
||||
return {
|
||||
context: this.#id,
|
||||
sandbox: this.#sandbox,
|
||||
sandbox: this.#sandbox.name,
|
||||
};
|
||||
}
|
||||
|
||||
setFrame(frame: BidiFrame): void {
|
||||
this.#frame = frame;
|
||||
setSandbox(sandbox: Sandbox): void {
|
||||
this.#sandbox = sandbox;
|
||||
|
||||
// TODO: Tack correct realm similar to BrowsingContexts
|
||||
this.connection.on(Bidi.ChromiumBidi.Script.EventNames.RealmCreated, () => {
|
||||
void this.#sandbox.taskManager.rerunAll();
|
||||
});
|
||||
// TODO(jrandolf): We should try to find a less brute-force way of doing
|
||||
// this.
|
||||
this.connection.on(
|
||||
@ -131,6 +134,8 @@ export class Realm extends EventEmitter {
|
||||
PuppeteerURL.INTERNAL_URL
|
||||
);
|
||||
|
||||
const sandbox = this.#sandbox;
|
||||
|
||||
let responsePromise;
|
||||
const resultOwnership = returnByValue
|
||||
? Bidi.Script.ResultOwnership.None
|
||||
@ -156,7 +161,7 @@ export class Realm extends EventEmitter {
|
||||
functionDeclaration,
|
||||
arguments: await Promise.all(
|
||||
args.map(arg => {
|
||||
return BidiSerializer.serialize(arg, this as any);
|
||||
return BidiSerializer.serialize(sandbox, arg);
|
||||
})
|
||||
),
|
||||
target: this.target,
|
||||
@ -174,20 +179,19 @@ export class Realm extends EventEmitter {
|
||||
|
||||
return returnByValue
|
||||
? BidiSerializer.deserialize(result.result)
|
||||
: getBidiHandle(this as any, result.result, this.#frame);
|
||||
: createBidiHandle(sandbox, result.result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function getBidiHandle(
|
||||
realmOrContext: Realm,
|
||||
result: Bidi.Script.RemoteValue,
|
||||
frame: BidiFrame
|
||||
): BidiJSHandle | BidiElementHandle<Node> {
|
||||
export function createBidiHandle(
|
||||
sandbox: Sandbox,
|
||||
result: Bidi.Script.RemoteValue
|
||||
): BidiJSHandle<unknown> | BidiElementHandle<Node> {
|
||||
if (result.type === 'node' || result.type === 'window') {
|
||||
return new BidiElementHandle(realmOrContext, result, frame);
|
||||
return new BidiElementHandle(sandbox, result);
|
||||
}
|
||||
return new BidiJSHandle(realmOrContext, result);
|
||||
return new BidiJSHandle(sandbox, result);
|
||||
}
|
||||
|
@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
|
||||
import {JSHandle} from '../../api/JSHandle.js';
|
||||
import {Realm} from '../../api/Realm.js';
|
||||
import {TimeoutSettings} from '../TimeoutSettings.js';
|
||||
@ -23,7 +21,7 @@ import {EvaluateFunc, HandleFor} from '../types.js';
|
||||
import {withSourcePuppeteerURLIfNone} from '../util.js';
|
||||
|
||||
import {BrowsingContext} from './BrowsingContext.js';
|
||||
import {BidiJSHandle} from './JSHandle.js';
|
||||
import {BidiFrame} from './Frame.js';
|
||||
import {Realm as BidiRealm} from './Realm.js';
|
||||
/**
|
||||
* A unique key for {@link SandboxChart} to denote the default world.
|
||||
@ -53,23 +51,26 @@ export interface SandboxChart {
|
||||
* @internal
|
||||
*/
|
||||
export class Sandbox extends Realm {
|
||||
#realm: BidiRealm;
|
||||
readonly name: string | undefined;
|
||||
readonly realm: BidiRealm;
|
||||
#frame: BidiFrame;
|
||||
|
||||
constructor(
|
||||
name: string | undefined,
|
||||
frame: BidiFrame,
|
||||
// TODO: We should split the Realm and BrowsingContext
|
||||
realm: BidiRealm | BrowsingContext,
|
||||
timeoutSettings: TimeoutSettings
|
||||
) {
|
||||
super(timeoutSettings);
|
||||
this.#realm = realm;
|
||||
|
||||
// TODO: Tack correct realm similar to BrowsingContexts
|
||||
this.#realm.connection.on(
|
||||
Bidi.ChromiumBidi.Script.EventNames.RealmCreated,
|
||||
() => {
|
||||
void this.taskManager.rerunAll();
|
||||
this.name = name;
|
||||
this.realm = realm;
|
||||
this.#frame = frame;
|
||||
this.realm.setSandbox(this);
|
||||
}
|
||||
);
|
||||
|
||||
override get environment(): BidiFrame {
|
||||
return this.#frame;
|
||||
}
|
||||
|
||||
async evaluateHandle<
|
||||
@ -83,7 +84,7 @@ export class Sandbox extends Realm {
|
||||
this.evaluateHandle.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.#realm.evaluateHandle(pageFunction, ...args);
|
||||
return await this.realm.evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
async evaluate<
|
||||
@ -97,7 +98,7 @@ export class Sandbox extends Realm {
|
||||
this.evaluate.name,
|
||||
pageFunction
|
||||
);
|
||||
return await this.#realm.evaluate(pageFunction, ...args);
|
||||
return await this.realm.evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||
@ -107,7 +108,7 @@ export class Sandbox extends Realm {
|
||||
}
|
||||
|
||||
async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||
if ((handle as unknown as BidiJSHandle).context() === this.#realm) {
|
||||
if (handle.realm === this) {
|
||||
return handle;
|
||||
}
|
||||
const transferredHandle = await this.evaluateHandle(node => {
|
||||
|
@ -19,9 +19,9 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
import {LazyArg} from '../LazyArg.js';
|
||||
import {debugError, isDate, isPlainObject, isRegExp} from '../util.js';
|
||||
|
||||
import {BrowsingContext} from './BrowsingContext.js';
|
||||
import {BidiElementHandle} from './ElementHandle.js';
|
||||
import {BidiJSHandle} from './JSHandle.js';
|
||||
import {Sandbox} from './Sandbox.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -58,7 +58,7 @@ export class BidiSerializer {
|
||||
};
|
||||
} else if (Array.isArray(arg)) {
|
||||
const parsedArray = arg.map(subArg => {
|
||||
return BidiSerializer.serializeRemoveValue(subArg);
|
||||
return BidiSerializer.serializeRemoteValue(subArg);
|
||||
});
|
||||
|
||||
return {
|
||||
@ -81,8 +81,8 @@ export class BidiSerializer {
|
||||
const parsedObject: Bidi.Script.MappingLocalValue = [];
|
||||
for (const key in arg) {
|
||||
parsedObject.push([
|
||||
BidiSerializer.serializeRemoveValue(key),
|
||||
BidiSerializer.serializeRemoveValue(arg[key]),
|
||||
BidiSerializer.serializeRemoteValue(key),
|
||||
BidiSerializer.serializeRemoteValue(arg[key]),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ export class BidiSerializer {
|
||||
);
|
||||
}
|
||||
|
||||
static serializeRemoveValue(arg: unknown): Bidi.Script.LocalValue {
|
||||
static serializeRemoteValue(arg: unknown): Bidi.Script.LocalValue {
|
||||
switch (typeof arg) {
|
||||
case 'symbol':
|
||||
case 'function':
|
||||
@ -143,11 +143,11 @@ export class BidiSerializer {
|
||||
}
|
||||
|
||||
static async serialize(
|
||||
arg: unknown,
|
||||
context: BrowsingContext
|
||||
sandbox: Sandbox,
|
||||
arg: unknown
|
||||
): Promise<Bidi.Script.LocalValue> {
|
||||
if (arg instanceof LazyArg) {
|
||||
arg = await arg.get(context);
|
||||
arg = await arg.get(sandbox.realm);
|
||||
}
|
||||
// eslint-disable-next-line rulesdir/use-using -- We want this to continue living.
|
||||
const objectHandle =
|
||||
@ -156,7 +156,7 @@ export class BidiSerializer {
|
||||
: null;
|
||||
if (objectHandle) {
|
||||
if (
|
||||
objectHandle.context() !== context &&
|
||||
objectHandle.realm !== sandbox &&
|
||||
!('sharedId' in objectHandle.remoteValue())
|
||||
) {
|
||||
throw new Error(
|
||||
@ -169,7 +169,7 @@ export class BidiSerializer {
|
||||
return objectHandle.remoteValue() as Bidi.Script.RemoteReference;
|
||||
}
|
||||
|
||||
return BidiSerializer.serializeRemoveValue(arg);
|
||||
return BidiSerializer.serializeRemoteValue(arg);
|
||||
}
|
||||
|
||||
static deserializeNumber(value: Bidi.Script.SpecialNumber | number): number {
|
||||
|
@ -30,7 +30,7 @@ import type {CDPSession} from './Connection.js';
|
||||
import {debug} from './Debug.js';
|
||||
import {CDPElementHandle} from './ElementHandle.js';
|
||||
import type {CommonEventEmitter} from './EventEmitter.js';
|
||||
import type {ExecutionContext} from './ExecutionContext.js';
|
||||
import {IsolatedWorld} from './IsolatedWorld.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {Awaitable} from './types.js';
|
||||
|
||||
@ -413,14 +413,14 @@ export async function waitForEvent<T>(
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function createJSHandle(
|
||||
context: ExecutionContext,
|
||||
export function createCdpHandle(
|
||||
realm: IsolatedWorld,
|
||||
remoteObject: Protocol.Runtime.RemoteObject
|
||||
): JSHandle | ElementHandle<Node> {
|
||||
if (remoteObject.subtype === 'node' && context._world) {
|
||||
return new CDPElementHandle(context, remoteObject, context._world.frame());
|
||||
if (remoteObject.subtype === 'node') {
|
||||
return new CDPElementHandle(realm, remoteObject);
|
||||
}
|
||||
return new CDPJSHandle(context, remoteObject);
|
||||
return new CDPJSHandle(realm, remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -755,12 +755,6 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw a nice error after a navigation",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw if elementHandles are from other frames",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -2267,12 +2261,6 @@
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw a nice error after a navigation",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["cdp", "firefox"],
|
||||
"expectations": ["FAIL", "TIMEOUT"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw when evaluation triggers reload",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
|
@ -419,26 +419,6 @@ describe('Evaluation specs', function () {
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
it('should throw a nice error after a navigation', async () => {
|
||||
const {page} = await getTestState();
|
||||
|
||||
const executionContext = await page.mainFrame().executionContext();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
executionContext.evaluate(() => {
|
||||
return window.location.reload();
|
||||
}),
|
||||
]);
|
||||
const error = await executionContext
|
||||
.evaluate(() => {
|
||||
return null;
|
||||
})
|
||||
.catch(error_ => {
|
||||
return error_;
|
||||
});
|
||||
expect((error as Error).message).toContain('navigation');
|
||||
});
|
||||
it('should not throw an error when evaluation does a navigation', async () => {
|
||||
const {page, server} = await getTestState();
|
||||
|
||||
|
@ -30,43 +30,6 @@ import {
|
||||
describe('Frame specs', function () {
|
||||
setupTestBrowserHooks();
|
||||
|
||||
describe('Frame.executionContext', function () {
|
||||
it('should work', async () => {
|
||||
const {page, server} = await getTestState();
|
||||
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await attachFrame(page, 'frame1', server.EMPTY_PAGE);
|
||||
expect(page.frames()).toHaveLength(2);
|
||||
const [frame1, frame2] = page.frames();
|
||||
const context1 = await frame1!.executionContext();
|
||||
const context2 = await frame2!.executionContext();
|
||||
expect(context1).toBeTruthy();
|
||||
expect(context2).toBeTruthy();
|
||||
expect(context1 !== context2).toBeTruthy();
|
||||
expect(context1._world?.frame()).toBe(frame1);
|
||||
expect(context2._world?.frame()).toBe(frame2);
|
||||
|
||||
await Promise.all([
|
||||
context1.evaluate(() => {
|
||||
return ((globalThis as any).a = 1);
|
||||
}),
|
||||
context2.evaluate(() => {
|
||||
return ((globalThis as any).a = 2);
|
||||
}),
|
||||
]);
|
||||
const [a1, a2] = await Promise.all([
|
||||
context1.evaluate(() => {
|
||||
return (globalThis as any).a;
|
||||
}),
|
||||
context2.evaluate(() => {
|
||||
return (globalThis as any).a;
|
||||
}),
|
||||
]);
|
||||
expect(a1).toBe(1);
|
||||
expect(a2).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Frame.evaluateHandle', function () {
|
||||
it('should work', async () => {
|
||||
const {page, server} = await getTestState();
|
||||
@ -338,7 +301,7 @@ describe('Frame specs', function () {
|
||||
describe('Frame.client', function () {
|
||||
it('should return the client instance', async () => {
|
||||
const {page} = await getTestState();
|
||||
expect(page.mainFrame()._client()).toBeInstanceOf(CDPSession);
|
||||
expect(page.mainFrame().client).toBeInstanceOf(CDPSession);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -102,7 +102,7 @@ describe('Workers', function () {
|
||||
return new Worker(`data:text/javascript,console.log(1)`);
|
||||
});
|
||||
const worker = await workerCreatedPromise;
|
||||
expect(await (await worker.executionContext()).evaluate('1+1')).toBe(2);
|
||||
expect(await worker.evaluate('1+1')).toBe(2);
|
||||
});
|
||||
it('should report errors', async () => {
|
||||
const {page} = await getTestState();
|
||||
|
Loading…
Reference in New Issue
Block a user