refactor: move binding called handling (#12410)

This commit is contained in:
Alex Rudenko 2024-05-08 14:24:06 +02:00 committed by GitHub
parent d0cd710e49
commit 83a97164ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 71 additions and 56 deletions

View File

@ -67,13 +67,15 @@ export class ExecutionContext
/** Emitted when this execution context is disposed. */ /** Emitted when this execution context is disposed. */
disposed: undefined; disposed: undefined;
consoleapicalled: Protocol.Runtime.ConsoleAPICalledEvent; consoleapicalled: Protocol.Runtime.ConsoleAPICalledEvent;
/** Emitted when a binding that is not installed by the ExecutionContext is called. */
bindingcalled: Protocol.Runtime.BindingCalledEvent;
}> }>
implements Disposable implements Disposable
{ {
_client: CDPSession; #client: CDPSession;
_world: IsolatedWorld; #world: IsolatedWorld;
_contextId: number; #id: number;
_contextName?: string; #name?: string;
readonly #disposables = new DisposableStack(); readonly #disposables = new DisposableStack();
@ -83,16 +85,16 @@ export class ExecutionContext
world: IsolatedWorld world: IsolatedWorld
) { ) {
super(); super();
this._client = client; this.#client = client;
this._world = world; this.#world = world;
this._contextId = contextPayload.id; this.#id = contextPayload.id;
if (contextPayload.name) { if (contextPayload.name) {
this._contextName = contextPayload.name; this.#name = contextPayload.name;
} }
const clientEmitter = this.#disposables.use(new EventEmitter(this._client)); const clientEmitter = this.#disposables.use(new EventEmitter(this.#client));
clientEmitter.on('Runtime.bindingCalled', this.#onBindingCalled.bind(this)); clientEmitter.on('Runtime.bindingCalled', this.#onBindingCalled.bind(this));
clientEmitter.on('Runtime.executionContextDestroyed', async event => { clientEmitter.on('Runtime.executionContextDestroyed', async event => {
if (event.executionContextId === this._contextId) { if (event.executionContextId === this.#id) {
this[disposeSymbol](); this[disposeSymbol]();
} }
}); });
@ -118,16 +120,16 @@ export class ExecutionContext
using _ = await this.#mutex.acquire(); using _ = await this.#mutex.acquire();
try { try {
await this._client.send( await this.#client.send(
'Runtime.addBinding', 'Runtime.addBinding',
this._contextName this.#name
? { ? {
name: binding.name, name: binding.name,
executionContextName: this._contextName, executionContextName: this.#name,
} }
: { : {
name: binding.name, name: binding.name,
executionContextId: this._contextId, executionContextId: this.#id,
} }
); );
@ -166,14 +168,16 @@ export class ExecutionContext
} }
const {type, name, seq, args, isTrivial} = payload; const {type, name, seq, args, isTrivial} = payload;
if (type !== 'internal') { if (type !== 'internal') {
this.emit('bindingcalled', event);
return; return;
} }
if (!this.#bindings.has(name)) { if (!this.#bindings.has(name)) {
this.emit('bindingcalled', event);
return; return;
} }
try { try {
if (event.executionContextId !== this._contextId) { if (event.executionContextId !== this.#id) {
return; return;
} }
@ -184,8 +188,12 @@ export class ExecutionContext
} }
} }
get id(): number {
return this.#id;
}
#onConsoleAPI(event: Protocol.Runtime.ConsoleAPICalledEvent): void { #onConsoleAPI(event: Protocol.Runtime.ConsoleAPICalledEvent): void {
if (event.executionContextId !== this._contextId) { if (event.executionContextId !== this.#id) {
return; return;
} }
this.emit('consoleapicalled', event); this.emit('consoleapicalled', event);
@ -365,13 +373,13 @@ export class ExecutionContext
); );
if (isString(pageFunction)) { if (isString(pageFunction)) {
const contextId = this._contextId; const contextId = this.#id;
const expression = pageFunction; const expression = pageFunction;
const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression)
? expression ? expression
: `${expression}\n${sourceUrlComment}\n`; : `${expression}\n${sourceUrlComment}\n`;
const {exceptionDetails, result: remoteObject} = await this._client const {exceptionDetails, result: remoteObject} = await this.#client
.send('Runtime.evaluate', { .send('Runtime.evaluate', {
expression: expressionWithSourceUrl, expression: expressionWithSourceUrl,
contextId, contextId,
@ -387,7 +395,7 @@ export class ExecutionContext
return returnByValue return returnByValue
? valueFromRemoteObject(remoteObject) ? valueFromRemoteObject(remoteObject)
: this._world.createCdpHandle(remoteObject); : this.#world.createCdpHandle(remoteObject);
} }
const functionDeclaration = stringifyFunction(pageFunction); const functionDeclaration = stringifyFunction(pageFunction);
@ -398,9 +406,9 @@ export class ExecutionContext
: `${functionDeclaration}\n${sourceUrlComment}\n`; : `${functionDeclaration}\n${sourceUrlComment}\n`;
let callFunctionOnPromise; let callFunctionOnPromise;
try { try {
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', { callFunctionOnPromise = this.#client.send('Runtime.callFunctionOn', {
functionDeclaration: functionDeclarationWithSourceUrl, functionDeclaration: functionDeclarationWithSourceUrl,
executionContextId: this._contextId, executionContextId: this.#id,
arguments: args.length arguments: args.length
? await Promise.all(args.map(convertArgument.bind(this))) ? await Promise.all(args.map(convertArgument.bind(this)))
: [], : [],
@ -424,7 +432,7 @@ export class ExecutionContext
} }
return returnByValue return returnByValue
? valueFromRemoteObject(remoteObject) ? valueFromRemoteObject(remoteObject)
: this._world.createCdpHandle(remoteObject); : this.#world.createCdpHandle(remoteObject);
async function convertArgument( async function convertArgument(
this: ExecutionContext, this: ExecutionContext,
@ -454,7 +462,7 @@ export class ExecutionContext
? arg ? arg
: null; : null;
if (objectHandle) { if (objectHandle) {
if (objectHandle.realm !== this._world) { 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!'
); );

View File

@ -80,6 +80,10 @@ export class CdpFrame extends Frame {
'consoleapicalled', 'consoleapicalled',
this.#onMainWorldConsoleApiCalled.bind(this) this.#onMainWorldConsoleApiCalled.bind(this)
); );
this.worlds[MAIN_WORLD].emitter.on(
'bindingcalled',
this.#onMainWorldBindingCalled.bind(this)
);
} }
#onMainWorldConsoleApiCalled( #onMainWorldConsoleApiCalled(
@ -91,6 +95,13 @@ export class CdpFrame extends Frame {
]); ]);
} }
#onMainWorldBindingCalled(event: Protocol.Runtime.BindingCalledEvent) {
this._frameManager.emit(FrameManagerEvent.BindingCalled, [
this.worlds[MAIN_WORLD],
event,
]);
}
/** /**
* This is used internally in DevTools. * This is used internally in DevTools.
* *

View File

@ -41,7 +41,6 @@ export class FrameManager extends EventEmitter<FrameManagerEvents> {
#page: CdpPage; #page: CdpPage;
#networkManager: NetworkManager; #networkManager: NetworkManager;
#timeoutSettings: TimeoutSettings; #timeoutSettings: TimeoutSettings;
#contextIdToContext = new Map<string, ExecutionContext>();
#isolatedWorlds = new Set<string>(); #isolatedWorlds = new Set<string>();
#client: CDPSession; #client: CDPSession;
@ -223,22 +222,6 @@ export class FrameManager extends EventEmitter<FrameManagerEvents> {
} }
} }
executionContextById(
contextId: number,
session: CDPSession = this.#client
): ExecutionContext {
const context = this.getExecutionContextById(contextId, session);
assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
return context;
}
getExecutionContextById(
contextId: number,
session: CDPSession = this.#client
): ExecutionContext | undefined {
return this.#contextIdToContext.get(`${session.id()}:${contextId}`);
}
page(): CdpPage { page(): CdpPage {
return this.#page; return this.#page;
} }
@ -491,16 +474,6 @@ export class FrameManager extends EventEmitter<FrameManagerEvents> {
world world
); );
world.setContext(context); world.setContext(context);
const key = `${session.id()}:${contextPayload.id}`;
this.#contextIdToContext.set(key, context);
context.once('disposed', () => {
const key = `${session.id()}:${contextPayload.id}`;
const context = this.#contextIdToContext.get(key);
if (!context) {
return;
}
this.#contextIdToContext.delete(key);
});
} }
#removeFramesRecursively(frame: CdpFrame): void { #removeFramesRecursively(frame: CdpFrame): void {

View File

@ -28,6 +28,7 @@ export namespace FrameManagerEvent {
'FrameManager.FrameNavigatedWithinDocument' 'FrameManager.FrameNavigatedWithinDocument'
); );
export const ConsoleApiCalled = Symbol('FrameManager.ConsoleApiCalled'); export const ConsoleApiCalled = Symbol('FrameManager.ConsoleApiCalled');
export const BindingCalled = Symbol('FrameManager.BindingCalled');
} }
/** /**
@ -45,4 +46,8 @@ export interface FrameManagerEvents extends Record<EventType, unknown> {
IsolatedWorld, IsolatedWorld,
Protocol.Runtime.ConsoleAPICalledEvent, Protocol.Runtime.ConsoleAPICalledEvent,
]; ];
[FrameManagerEvent.BindingCalled]: [
IsolatedWorld,
Protocol.Runtime.BindingCalledEvent,
];
} }

View File

@ -54,6 +54,8 @@ type IsolatedWorldEmitter = EventEmitter<{
disposed: undefined; disposed: undefined;
// Emitted when a new console message is logged. // Emitted when a new console message is logged.
consoleapicalled: Protocol.Runtime.ConsoleAPICalledEvent; consoleapicalled: Protocol.Runtime.ConsoleAPICalledEvent;
/** Emitted when a binding that is not installed by the ExecutionContext is called. */
bindingcalled: Protocol.Runtime.BindingCalledEvent;
}>; }>;
/** /**
@ -89,6 +91,7 @@ export class IsolatedWorld extends Realm {
this.#context?.[disposeSymbol](); this.#context?.[disposeSymbol]();
context.once('disposed', this.#onContextDisposed.bind(this)); context.once('disposed', this.#onContextDisposed.bind(this));
context.on('consoleapicalled', this.#onContextConsoleApiCalled.bind(this)); context.on('consoleapicalled', this.#onContextConsoleApiCalled.bind(this));
context.on('bindingcalled', this.#onContextBindingCalled.bind(this));
this.#context = context; this.#context = context;
this.#emitter.emit('context', context); this.#emitter.emit('context', context);
void this.taskManager.rerunAll(); void this.taskManager.rerunAll();
@ -107,10 +110,18 @@ export class IsolatedWorld extends Realm {
this.#emitter.emit('consoleapicalled', event); this.#emitter.emit('consoleapicalled', event);
} }
#onContextBindingCalled(event: Protocol.Runtime.BindingCalledEvent): void {
this.#emitter.emit('bindingcalled', event);
}
hasContext(): boolean { hasContext(): boolean {
return !!this.#context; return !!this.#context;
} }
get context(): ExecutionContext | undefined {
return this.#context;
}
#executionContext(): ExecutionContext | undefined { #executionContext(): ExecutionContext | undefined {
if (this.disposed) { if (this.disposed) {
throw new Error( throw new Error(
@ -193,7 +204,7 @@ export class IsolatedWorld extends Realm {
} }
const {object} = await this.client.send('DOM.resolveNode', { const {object} = await this.client.send('DOM.resolveNode', {
backendNodeId: backendNodeId, backendNodeId: backendNodeId,
executionContextId: context._contextId, executionContextId: context.id,
}); });
return this.createCdpHandle(object) as JSHandle<Node>; return this.createCdpHandle(object) as JSHandle<Node>;
} }

View File

@ -217,7 +217,6 @@ export class CdpPage extends Page {
return this.emit(PageEvent.Load, undefined); return this.emit(PageEvent.Load, undefined);
}, },
], ],
['Runtime.bindingCalled', this.#onBindingCalled.bind(this)],
['Page.javascriptDialogOpening', this.#onDialog.bind(this)], ['Page.javascriptDialogOpening', this.#onDialog.bind(this)],
['Runtime.exceptionThrown', this.#handleException.bind(this)], ['Runtime.exceptionThrown', this.#handleException.bind(this)],
['Inspector.targetCrashed', this.#onTargetCrashed.bind(this)], ['Inspector.targetCrashed', this.#onTargetCrashed.bind(this)],
@ -259,6 +258,16 @@ export class CdpPage extends Page {
} }
); );
this.#frameManager.on(
FrameManagerEvent.BindingCalled,
([world, event]: [
IsolatedWorld,
Protocol.Runtime.BindingCalledEvent,
]) => {
void this.#onBindingCalled(world, event);
}
);
for (const [eventName, handler] of this.#networkManagerHandlers) { for (const [eventName, handler] of this.#networkManagerHandlers) {
// TODO: Remove any. // TODO: Remove any.
this.#frameManager.networkManager.on(eventName, handler as any); this.#frameManager.networkManager.on(eventName, handler as any);
@ -803,6 +812,7 @@ export class CdpPage extends Page {
} }
async #onBindingCalled( async #onBindingCalled(
world: IsolatedWorld,
event: Protocol.Runtime.BindingCalledEvent event: Protocol.Runtime.BindingCalledEvent
): Promise<void> { ): Promise<void> {
let payload: BindingPayload; let payload: BindingPayload;
@ -818,10 +828,7 @@ export class CdpPage extends Page {
return; return;
} }
const context = this.#frameManager.executionContextById( const context = world.context;
event.executionContextId,
this.#primaryTargetClient
);
if (!context) { if (!context) {
return; return;
} }