mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
refactor: improve ExecutionContext internals (#12399)
This commit is contained in:
parent
5a05838b62
commit
91e9503624
@ -6,9 +6,10 @@
|
|||||||
|
|
||||||
import type {Protocol} from 'devtools-protocol';
|
import type {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
import type {CDPSession} from '../api/CDPSession.js';
|
import type {CDPSession, CDPSessionEvents} from '../api/CDPSession.js';
|
||||||
import type {ElementHandle} from '../api/ElementHandle.js';
|
import type {ElementHandle} from '../api/ElementHandle.js';
|
||||||
import type {JSHandle} from '../api/JSHandle.js';
|
import type {JSHandle} from '../api/JSHandle.js';
|
||||||
|
import {EventEmitter} from '../common/EventEmitter.js';
|
||||||
import {LazyArg} from '../common/LazyArg.js';
|
import {LazyArg} from '../common/LazyArg.js';
|
||||||
import {scriptInjector} from '../common/ScriptInjector.js';
|
import {scriptInjector} from '../common/ScriptInjector.js';
|
||||||
import type {BindingPayload, EvaluateFunc, HandleFor} from '../common/types.js';
|
import type {BindingPayload, EvaluateFunc, HandleFor} from '../common/types.js';
|
||||||
@ -22,7 +23,7 @@ import {
|
|||||||
} from '../common/util.js';
|
} from '../common/util.js';
|
||||||
import type PuppeteerUtil from '../injected/injected.js';
|
import type PuppeteerUtil from '../injected/injected.js';
|
||||||
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
|
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
|
||||||
import {disposeSymbol} from '../util/disposable.js';
|
import {DisposableStack, disposeSymbol} from '../util/disposable.js';
|
||||||
import {stringifyFunction} from '../util/Function.js';
|
import {stringifyFunction} from '../util/Function.js';
|
||||||
import {Mutex} from '../util/Mutex.js';
|
import {Mutex} from '../util/Mutex.js';
|
||||||
|
|
||||||
@ -37,6 +38,27 @@ import {
|
|||||||
valueFromRemoteObject,
|
valueFromRemoteObject,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
|
||||||
|
const ariaQuerySelectorBinding = new Binding(
|
||||||
|
'__ariaQuerySelector',
|
||||||
|
ARIAQueryHandler.queryOne as (...args: unknown[]) => unknown
|
||||||
|
);
|
||||||
|
|
||||||
|
const ariaQuerySelectorAllBinding = new Binding(
|
||||||
|
'__ariaQuerySelectorAll',
|
||||||
|
(async (
|
||||||
|
element: ElementHandle<Node>,
|
||||||
|
selector: string
|
||||||
|
): Promise<JSHandle<Node[]>> => {
|
||||||
|
const results = ARIAQueryHandler.queryAll(element, selector);
|
||||||
|
return await element.realm.evaluateHandle(
|
||||||
|
(...elements) => {
|
||||||
|
return elements;
|
||||||
|
},
|
||||||
|
...(await AsyncIterableUtil.collect(results))
|
||||||
|
);
|
||||||
|
}) as (...args: unknown[]) => unknown
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -46,6 +68,9 @@ export class ExecutionContext implements Disposable {
|
|||||||
_contextId: number;
|
_contextId: number;
|
||||||
_contextName?: string;
|
_contextName?: string;
|
||||||
|
|
||||||
|
readonly #disposables = new DisposableStack();
|
||||||
|
readonly #clientEmitter: EventEmitter<CDPSessionEvents>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
client: CDPSession,
|
client: CDPSession,
|
||||||
contextPayload: Protocol.Runtime.ExecutionContextDescription,
|
contextPayload: Protocol.Runtime.ExecutionContextDescription,
|
||||||
@ -57,20 +82,21 @@ export class ExecutionContext implements Disposable {
|
|||||||
if (contextPayload.name) {
|
if (contextPayload.name) {
|
||||||
this._contextName = contextPayload.name;
|
this._contextName = contextPayload.name;
|
||||||
}
|
}
|
||||||
this._client.on('Runtime.bindingCalled', this.#onBindingCalled);
|
this.#clientEmitter = this.#disposables.use(new EventEmitter(this._client));
|
||||||
|
this.#clientEmitter.on(
|
||||||
|
'Runtime.bindingCalled',
|
||||||
|
this.#onBindingCalled.bind(this)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set of bindings that have been registered in the current context.
|
|
||||||
#contextBindings = new Set<string>();
|
|
||||||
|
|
||||||
// Contains mapping from functions that should be bound to Puppeteer functions.
|
// Contains mapping from functions that should be bound to Puppeteer functions.
|
||||||
#bindings = new Map<string, Binding>();
|
#bindings = new Map<string, Binding>();
|
||||||
|
|
||||||
// If multiple waitFor are set up asynchronously, we need to wait for the
|
// If multiple waitFor are set up asynchronously, we need to wait for the
|
||||||
// first one to set up the binding in the page before running the others.
|
// first one to set up the binding in the page before running the others.
|
||||||
#mutex = new Mutex();
|
#mutex = new Mutex();
|
||||||
async _addBindingToContext(name: string): Promise<void> {
|
async #addBinding(binding: Binding): Promise<void> {
|
||||||
if (this.#contextBindings.has(name)) {
|
if (this.#bindings.has(binding.name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,18 +106,18 @@ export class ExecutionContext implements Disposable {
|
|||||||
'Runtime.addBinding',
|
'Runtime.addBinding',
|
||||||
this._contextName
|
this._contextName
|
||||||
? {
|
? {
|
||||||
name,
|
name: binding.name,
|
||||||
executionContextName: this._contextName,
|
executionContextName: this._contextName,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name,
|
name: binding.name,
|
||||||
executionContextId: this._contextId,
|
executionContextId: this._contextId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.evaluate(addPageBinding, 'internal', name);
|
await this.evaluate(addPageBinding, 'internal', binding.name);
|
||||||
|
|
||||||
this.#contextBindings.add(name);
|
this.#bindings.set(binding.name, binding);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// We could have tried to evaluate in a context which was already
|
// We could have tried to evaluate in a context which was already
|
||||||
// destroyed. This happens, for example, if the page is navigated while
|
// destroyed. This happens, for example, if the page is navigated while
|
||||||
@ -111,9 +137,9 @@ export class ExecutionContext implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#onBindingCalled = async (
|
async #onBindingCalled(
|
||||||
event: Protocol.Runtime.BindingCalledEvent
|
event: Protocol.Runtime.BindingCalledEvent
|
||||||
): Promise<void> => {
|
): Promise<void> {
|
||||||
let payload: BindingPayload;
|
let payload: BindingPayload;
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(event.payload);
|
payload = JSON.parse(event.payload);
|
||||||
@ -126,7 +152,7 @@ export class ExecutionContext implements Disposable {
|
|||||||
if (type !== 'internal') {
|
if (type !== 'internal') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.#contextBindings.has(name)) {
|
if (!this.#bindings.has(name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +166,7 @@ export class ExecutionContext implements Disposable {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
debugError(err);
|
debugError(err);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
#bindingsInstalled = false;
|
#bindingsInstalled = false;
|
||||||
#puppeteerUtil?: Promise<JSHandle<PuppeteerUtil>>;
|
#puppeteerUtil?: Promise<JSHandle<PuppeteerUtil>>;
|
||||||
@ -148,26 +174,8 @@ export class ExecutionContext implements Disposable {
|
|||||||
let promise = Promise.resolve() as Promise<unknown>;
|
let promise = Promise.resolve() as Promise<unknown>;
|
||||||
if (!this.#bindingsInstalled) {
|
if (!this.#bindingsInstalled) {
|
||||||
promise = Promise.all([
|
promise = Promise.all([
|
||||||
this.#installGlobalBinding(
|
this.#addBindingWithoutThrowing(ariaQuerySelectorBinding),
|
||||||
new Binding(
|
this.#addBindingWithoutThrowing(ariaQuerySelectorAllBinding),
|
||||||
'__ariaQuerySelector',
|
|
||||||
ARIAQueryHandler.queryOne as (...args: unknown[]) => unknown
|
|
||||||
)
|
|
||||||
),
|
|
||||||
this.#installGlobalBinding(
|
|
||||||
new Binding('__ariaQuerySelectorAll', (async (
|
|
||||||
element: ElementHandle<Node>,
|
|
||||||
selector: string
|
|
||||||
): Promise<JSHandle<Node[]>> => {
|
|
||||||
const results = ARIAQueryHandler.queryAll(element, selector);
|
|
||||||
return await element.realm.evaluateHandle(
|
|
||||||
(...elements) => {
|
|
||||||
return elements;
|
|
||||||
},
|
|
||||||
...(await AsyncIterableUtil.collect(results))
|
|
||||||
);
|
|
||||||
}) as (...args: unknown[]) => unknown)
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
this.#bindingsInstalled = true;
|
this.#bindingsInstalled = true;
|
||||||
}
|
}
|
||||||
@ -184,16 +192,14 @@ export class ExecutionContext implements Disposable {
|
|||||||
return this.#puppeteerUtil as Promise<JSHandle<PuppeteerUtil>>;
|
return this.#puppeteerUtil as Promise<JSHandle<PuppeteerUtil>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #installGlobalBinding(binding: Binding) {
|
async #addBindingWithoutThrowing(binding: Binding) {
|
||||||
try {
|
try {
|
||||||
if (this._world) {
|
await this.#addBinding(binding);
|
||||||
this.#bindings.set(binding.name, binding);
|
} catch (err) {
|
||||||
await this._addBindingToContext(binding.name);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// If the binding cannot be added, then either the browser doesn't support
|
// If the binding cannot be added, then either the browser doesn't support
|
||||||
// bindings (e.g. Firefox) or the context is broken. Either breakage is
|
// bindings (e.g. Firefox) or the context is broken. Either breakage is
|
||||||
// okay, so we ignore the error.
|
// okay, so we ignore the error.
|
||||||
|
debugError(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,7 +455,7 @@ export class ExecutionContext implements Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[disposeSymbol](): void {
|
[disposeSymbol](): void {
|
||||||
this._client.off('Runtime.bindingCalled', this.#onBindingCalled);
|
this.#disposables.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user