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