fix: synchronize frame tree with the events processing (#11112)

This commit is contained in:
Alex Rudenko 2023-10-10 16:44:38 +02:00 committed by GitHub
parent 2811119562
commit d63f0cfc61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -98,6 +98,8 @@ export class FrameManager extends EventEmitter<FrameManagerEvents> {
DeviceRequestPromptManager DeviceRequestPromptManager
>(); >();
#frameTreeHandled?: Deferred<void>;
get timeoutSettings(): TimeoutSettings { get timeoutSettings(): TimeoutSettings {
return this.#timeoutSettings; return this.#timeoutSettings;
} }
@ -193,52 +195,69 @@ export class FrameManager extends EventEmitter<FrameManagerEvents> {
} }
private setupEventListeners(session: CDPSession) { private setupEventListeners(session: CDPSession) {
session.on('Page.frameAttached', event => { session.on('Page.frameAttached', async event => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onFrameAttached(session, event.frameId, event.parentFrameId); this.#onFrameAttached(session, event.frameId, event.parentFrameId);
}); });
session.on('Page.frameNavigated', event => { session.on('Page.frameNavigated', async event => {
this.#frameNavigatedReceived.add(event.frame.id); this.#frameNavigatedReceived.add(event.frame.id);
await this.#frameTreeHandled?.valueOrThrow();
void this.#onFrameNavigated(event.frame, event.type); void this.#onFrameNavigated(event.frame, event.type);
}); });
session.on('Page.navigatedWithinDocument', event => { session.on('Page.navigatedWithinDocument', async event => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onFrameNavigatedWithinDocument(event.frameId, event.url); this.#onFrameNavigatedWithinDocument(event.frameId, event.url);
}); });
session.on( session.on(
'Page.frameDetached', 'Page.frameDetached',
(event: Protocol.Page.FrameDetachedEvent) => { async (event: Protocol.Page.FrameDetachedEvent) => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onFrameDetached( this.#onFrameDetached(
event.frameId, event.frameId,
event.reason as Protocol.Page.FrameDetachedEventReason event.reason as Protocol.Page.FrameDetachedEventReason
); );
} }
); );
session.on('Page.frameStartedLoading', event => { session.on('Page.frameStartedLoading', async event => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onFrameStartedLoading(event.frameId); this.#onFrameStartedLoading(event.frameId);
}); });
session.on('Page.frameStoppedLoading', event => { session.on('Page.frameStoppedLoading', async event => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onFrameStoppedLoading(event.frameId); this.#onFrameStoppedLoading(event.frameId);
}); });
session.on('Runtime.executionContextCreated', event => { session.on('Runtime.executionContextCreated', async event => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onExecutionContextCreated(event.context, session); this.#onExecutionContextCreated(event.context, session);
}); });
session.on('Runtime.executionContextDestroyed', event => { session.on('Runtime.executionContextDestroyed', async event => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onExecutionContextDestroyed(event.executionContextId, session); this.#onExecutionContextDestroyed(event.executionContextId, session);
}); });
session.on('Runtime.executionContextsCleared', () => { session.on('Runtime.executionContextsCleared', async () => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onExecutionContextsCleared(session); this.#onExecutionContextsCleared(session);
}); });
session.on('Page.lifecycleEvent', event => { session.on('Page.lifecycleEvent', async event => {
await this.#frameTreeHandled?.valueOrThrow();
this.#onLifecycleEvent(event); this.#onLifecycleEvent(event);
}); });
} }
async initialize(client: CDPSession): Promise<void> { async initialize(client: CDPSession): Promise<void> {
try { try {
this.#frameTreeHandled?.resolve();
this.#frameTreeHandled = Deferred.create();
// We need to schedule all these commands while the target is paused,
// therefore, it needs to happen synchroniously. At the same time we
// should not start processing execution context and frame events before
// we received the initial information about the frame tree.
await Promise.all([ await Promise.all([
this.#networkManager.addClient(client), this.#networkManager.addClient(client),
client.send('Page.enable'), client.send('Page.enable'),
client.send('Page.getFrameTree').then(({frameTree}) => { client.send('Page.getFrameTree').then(({frameTree}) => {
this.#handleFrameTree(client, frameTree); this.#handleFrameTree(client, frameTree);
this.#frameTreeHandled?.resolve();
}), }),
client.send('Page.setLifecycleEventsEnabled', {enabled: true}), client.send('Page.setLifecycleEventsEnabled', {enabled: true}),
client.send('Runtime.enable').then(() => { client.send('Runtime.enable').then(() => {
@ -246,6 +265,7 @@ export class FrameManager extends EventEmitter<FrameManagerEvents> {
}), }),
]); ]);
} catch (error) { } catch (error) {
this.#frameTreeHandled?.resolve();
// The target might have been closed before the initialization finished. // The target might have been closed before the initialization finished.
if (isErrorLike(error) && isTargetClosedError(error)) { if (isErrorLike(error) && isTargetClosedError(error)) {
return; return;