fix(reuse): ignore late bindings after dispatchers were disposed (#22838)

When reusing the context, we first dispose all dispatchers, and then
reset the page/context. If bindings are triggered during the reset, they
try to send messages on disposed dispatchers.

Since there is no way to unregister bindings, just ignore them after
disposal.

Fixes #22803.
This commit is contained in:
Dmitry Gozman 2023-05-05 11:10:53 -07:00 committed by GitHub
parent 236c329ea9
commit 2393602e8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 0 deletions

View file

@ -169,6 +169,10 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
async exposeBinding(params: channels.BrowserContextExposeBindingParams): Promise<void> {
await this._context.exposeBinding(params.name, !!params.needsHandle, (source, ...args) => {
// When reusing the context, we might have some bindings called late enough,
// after context and page dispatchers have been disposed.
if (this._disposed)
return;
const pageDispatcher = PageDispatcher.from(this, source.page);
const binding = new BindingCallDispatcher(pageDispatcher, params.name, !!params.needsHandle, source, args);
this._dispatchEvent('bindingCall', { binding });

View file

@ -111,6 +111,10 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
async exposeBinding(params: channels.PageExposeBindingParams, metadata: CallMetadata): Promise<void> {
await this._page.exposeBinding(params.name, !!params.needsHandle, (source, ...args) => {
// When reusing the context, we might have some bindings called late enough,
// after context and page dispatchers have been disposed.
if (this._disposed)
return;
const binding = new BindingCallDispatcher(this, params.name, !!params.needsHandle, source, args);
this._dispatchEvent('bindingCall', { binding });
return binding.promise();

View file

@ -182,3 +182,23 @@ test('should not cache resources', async ({ reusedContext, server }) => {
expect(requestCountMap.get('/simple.json')).toBe(2);
}
});
test('should ignore binding from beforeunload', async ({ reusedContext }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/22803' });
let context = await reusedContext();
let called = false;
await context.exposeFunction('binding', () => called = true);
let page = await context.newPage();
await page.evaluate(() => {
window.addEventListener('beforeunload', () => window['binding']());
});
context = await reusedContext();
page = context.pages()[0];
await page.setContent('hello');
expect(called).toBe(false);
});