feat: make console/dialog events based on subscription (#22835)
This way we do not send events from the server unless the client is interested. Fixes #22621.
This commit is contained in:
parent
2393602e8c
commit
42328478ea
|
|
@ -107,6 +107,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
if (page)
|
if (page)
|
||||||
hasListeners = page.emit(Events.Page.Dialog, dialogObject) || hasListeners;
|
hasListeners = page.emit(Events.Page.Dialog, dialogObject) || hasListeners;
|
||||||
if (!hasListeners) {
|
if (!hasListeners) {
|
||||||
|
// Although we do similar handling on the server side, we still need this logic
|
||||||
|
// on the client side due to a possible race condition between two async calls:
|
||||||
|
// a) removing "dialog" listener subscription (client->server)
|
||||||
|
// b) actual "dialog" event (server->client)
|
||||||
if (dialogObject.type() === 'beforeunload')
|
if (dialogObject.type() === 'beforeunload')
|
||||||
dialog.accept({}).catch(() => {});
|
dialog.accept({}).catch(() => {});
|
||||||
else
|
else
|
||||||
|
|
@ -120,6 +124,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f));
|
this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f));
|
||||||
|
|
||||||
this._setEventToSubscriptionMapping(new Map<string, channels.BrowserContextUpdateSubscriptionParams['event']>([
|
this._setEventToSubscriptionMapping(new Map<string, channels.BrowserContextUpdateSubscriptionParams['event']>([
|
||||||
|
[Events.BrowserContext.Console, 'console'],
|
||||||
|
[Events.BrowserContext.Dialog, 'dialog'],
|
||||||
[Events.BrowserContext.Request, 'request'],
|
[Events.BrowserContext.Request, 'request'],
|
||||||
[Events.BrowserContext.Response, 'response'],
|
[Events.BrowserContext.Response, 'response'],
|
||||||
[Events.BrowserContext.RequestFinished, 'requestFinished'],
|
[Events.BrowserContext.RequestFinished, 'requestFinished'],
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,11 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
|
|
||||||
private _updateSubscription(event: string | symbol, enabled: boolean) {
|
private _updateSubscription(event: string | symbol, enabled: boolean) {
|
||||||
const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
|
const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
|
||||||
if (protocolEvent)
|
if (protocolEvent) {
|
||||||
(this._channel as any).updateSubscription({ event: protocolEvent, enabled }).catch(() => {});
|
this._wrapApiCall(async () => {
|
||||||
|
await (this._channel as any).updateSubscription({ event: protocolEvent, enabled });
|
||||||
|
}, true).catch(() => {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override on(event: string | symbol, listener: Listener): this {
|
override on(event: string | symbol, listener: Listener): this {
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
this.once(Events.Page.Crash, () => this._closedOrCrashedRace.scopeClosed(new Error(kBrowserOrContextClosedError)));
|
this.once(Events.Page.Crash, () => this._closedOrCrashedRace.scopeClosed(new Error(kBrowserOrContextClosedError)));
|
||||||
|
|
||||||
this._setEventToSubscriptionMapping(new Map<string, channels.PageUpdateSubscriptionParams['event']>([
|
this._setEventToSubscriptionMapping(new Map<string, channels.PageUpdateSubscriptionParams['event']>([
|
||||||
|
[Events.Page.Console, 'console'],
|
||||||
|
[Events.Page.Dialog, 'dialog'],
|
||||||
[Events.Page.Request, 'request'],
|
[Events.Page.Request, 'request'],
|
||||||
[Events.Page.Response, 'response'],
|
[Events.Page.Response, 'response'],
|
||||||
[Events.Page.RequestFinished, 'requestFinished'],
|
[Events.Page.RequestFinished, 'requestFinished'],
|
||||||
|
|
|
||||||
|
|
@ -924,7 +924,7 @@ scheme.BrowserContextCreateTempFileResult = tObject({
|
||||||
writableStream: tChannel(['WritableStream']),
|
writableStream: tChannel(['WritableStream']),
|
||||||
});
|
});
|
||||||
scheme.BrowserContextUpdateSubscriptionParams = tObject({
|
scheme.BrowserContextUpdateSubscriptionParams = tObject({
|
||||||
event: tEnum(['request', 'response', 'requestFinished', 'requestFailed']),
|
event: tEnum(['console', 'dialog', 'request', 'response', 'requestFinished', 'requestFailed']),
|
||||||
enabled: tBoolean,
|
enabled: tBoolean,
|
||||||
});
|
});
|
||||||
scheme.BrowserContextUpdateSubscriptionResult = tOptional(tObject({}));
|
scheme.BrowserContextUpdateSubscriptionResult = tOptional(tObject({}));
|
||||||
|
|
@ -1217,7 +1217,7 @@ scheme.PageStopCSSCoverageResult = tObject({
|
||||||
scheme.PageBringToFrontParams = tOptional(tObject({}));
|
scheme.PageBringToFrontParams = tOptional(tObject({}));
|
||||||
scheme.PageBringToFrontResult = tOptional(tObject({}));
|
scheme.PageBringToFrontResult = tOptional(tObject({}));
|
||||||
scheme.PageUpdateSubscriptionParams = tObject({
|
scheme.PageUpdateSubscriptionParams = tObject({
|
||||||
event: tEnum(['fileChooser', 'request', 'response', 'requestFinished', 'requestFailed']),
|
event: tEnum(['console', 'dialog', 'fileChooser', 'request', 'response', 'requestFinished', 'requestFailed']),
|
||||||
enabled: tBoolean,
|
enabled: tBoolean,
|
||||||
});
|
});
|
||||||
scheme.PageUpdateSubscriptionResult = tOptional(tObject({}));
|
scheme.PageUpdateSubscriptionResult = tOptional(tObject({}));
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ import { createGuid, urlMatches } from '../../utils';
|
||||||
import { WritableStreamDispatcher } from './writableStreamDispatcher';
|
import { WritableStreamDispatcher } from './writableStreamDispatcher';
|
||||||
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
|
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
|
||||||
import { DialogDispatcher } from './dialogDispatcher';
|
import { DialogDispatcher } from './dialogDispatcher';
|
||||||
|
import type { Page } from '../page';
|
||||||
|
import type { Dialog } from '../dialog';
|
||||||
|
import type { ConsoleMessage } from '../console';
|
||||||
|
|
||||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
|
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
|
||||||
_type_EventTarget = true;
|
_type_EventTarget = true;
|
||||||
|
|
@ -81,8 +84,16 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
this._dispatchEvent('close');
|
this._dispatchEvent('close');
|
||||||
this._dispose();
|
this._dispose();
|
||||||
});
|
});
|
||||||
this.addObjectListener(BrowserContext.Events.Console, message => this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(PageDispatcher.from(this, message.page()), message) }));
|
this.addObjectListener(BrowserContext.Events.Console, (message: ConsoleMessage) => {
|
||||||
this.addObjectListener(BrowserContext.Events.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this, dialog) }));
|
if (this._shouldDispatchEvent(message.page(), 'console'))
|
||||||
|
this._dispatchEvent('console', { message: new ConsoleMessageDispatcher(PageDispatcher.from(this, message.page()), message) });
|
||||||
|
});
|
||||||
|
this.addObjectListener(BrowserContext.Events.Dialog, (dialog: Dialog) => {
|
||||||
|
if (this._shouldDispatchEvent(dialog.page(), 'dialog'))
|
||||||
|
this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this, dialog) });
|
||||||
|
else
|
||||||
|
dialog.close().catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
if (context._browser.options.name === 'chromium') {
|
if (context._browser.options.name === 'chromium') {
|
||||||
for (const page of (context as CRBrowserContext).backgroundPages())
|
for (const page of (context as CRBrowserContext).backgroundPages())
|
||||||
|
|
@ -141,9 +152,12 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
}
|
}
|
||||||
|
|
||||||
private _shouldDispatchNetworkEvent(request: Request, event: channels.BrowserContextUpdateSubscriptionParams['event'] & channels.PageUpdateSubscriptionParams['event']): boolean {
|
private _shouldDispatchNetworkEvent(request: Request, event: channels.BrowserContextUpdateSubscriptionParams['event'] & channels.PageUpdateSubscriptionParams['event']): boolean {
|
||||||
|
return this._shouldDispatchEvent(request.frame()?._page?.initializedOrUndefined(), event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldDispatchEvent(page: Page | undefined, event: channels.BrowserContextUpdateSubscriptionParams['event'] & channels.PageUpdateSubscriptionParams['event']): boolean {
|
||||||
if (this._subscriptions.has(event))
|
if (this._subscriptions.has(event))
|
||||||
return true;
|
return true;
|
||||||
const page = request.frame()?._page?.initializedOrUndefined();
|
|
||||||
const pageDispatcher = page ? existingDispatcher<PageDispatcher>(page) : undefined;
|
const pageDispatcher = page ? existingDispatcher<PageDispatcher>(page) : undefined;
|
||||||
if (pageDispatcher?._subscriptions.has(event))
|
if (pageDispatcher?._subscriptions.has(event))
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ import type { HarTracerDelegate } from '../../har/harTracer';
|
||||||
import { HarTracer } from '../../har/harTracer';
|
import { HarTracer } from '../../har/harTracer';
|
||||||
import type { FrameSnapshot } from '@trace/snapshot';
|
import type { FrameSnapshot } from '@trace/snapshot';
|
||||||
import type * as trace from '@trace/trace';
|
import type * as trace from '@trace/trace';
|
||||||
import type { VERSION } from '@trace/trace';
|
|
||||||
import type { SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
import type { SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
||||||
import { Snapshotter } from './snapshotter';
|
import { Snapshotter } from './snapshotter';
|
||||||
import { yazl } from '../../../zipBundle';
|
import { yazl } from '../../../zipBundle';
|
||||||
|
import type { ConsoleMessage } from '../../console';
|
||||||
|
|
||||||
const version: VERSION = 4;
|
const version: trace.VERSION = 4;
|
||||||
|
|
||||||
export type TracerOptions = {
|
export type TracerOptions = {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
@ -71,6 +71,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
private _snapshotter?: Snapshotter;
|
private _snapshotter?: Snapshotter;
|
||||||
private _harTracer: HarTracer;
|
private _harTracer: HarTracer;
|
||||||
private _screencastListeners: RegisteredListener[] = [];
|
private _screencastListeners: RegisteredListener[] = [];
|
||||||
|
private _eventListeners: RegisteredListener[] = [];
|
||||||
private _context: BrowserContext | APIRequestContext;
|
private _context: BrowserContext | APIRequestContext;
|
||||||
private _state: RecordingState | undefined;
|
private _state: RecordingState | undefined;
|
||||||
private _isStopping = false;
|
private _isStopping = false;
|
||||||
|
|
@ -168,6 +169,9 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
});
|
});
|
||||||
|
|
||||||
this._context.instrumentation.addListener(this, this._context);
|
this._context.instrumentation.addListener(this, this._context);
|
||||||
|
this._eventListeners.push(
|
||||||
|
eventsHelper.addEventListener(this._context, BrowserContext.Events.Console, this._onConsoleMessage.bind(this)),
|
||||||
|
);
|
||||||
if (state.options.screenshots)
|
if (state.options.screenshots)
|
||||||
this._startScreencast();
|
this._startScreencast();
|
||||||
if (state.options.snapshots)
|
if (state.options.snapshots)
|
||||||
|
|
@ -248,6 +252,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
|
|
||||||
const state = this._state!;
|
const state = this._state!;
|
||||||
this._context.instrumentation.removeListener(this);
|
this._context.instrumentation.removeListener(this);
|
||||||
|
eventsHelper.removeEventListeners(this._eventListeners);
|
||||||
if (this._state?.options.screenshots)
|
if (this._state?.options.screenshots)
|
||||||
this._stopScreencast();
|
this._stopScreencast();
|
||||||
|
|
||||||
|
|
@ -354,14 +359,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
onEvent(sdkObject: SdkObject, event: trace.EventTraceEvent) {
|
onEvent(sdkObject: SdkObject, event: trace.EventTraceEvent) {
|
||||||
if (!sdkObject.attribution.context)
|
if (!sdkObject.attribution.context)
|
||||||
return;
|
return;
|
||||||
if (event.method === '__create__' && event.class === 'ConsoleMessage') {
|
if (event.method === 'console' || (event.method === '__create__' && event.class === 'ConsoleMessage')) {
|
||||||
const object: trace.ObjectTraceEvent = {
|
// Console messages are handled separately.
|
||||||
type: 'object',
|
|
||||||
class: event.class,
|
|
||||||
guid: event.params.guid,
|
|
||||||
initializer: event.params.initializer,
|
|
||||||
};
|
|
||||||
this._appendTraceEvent(object);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._appendTraceEvent(event);
|
this._appendTraceEvent(event);
|
||||||
|
|
@ -390,6 +389,30 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
this._appendTraceEvent({ type: 'frame-snapshot', snapshot });
|
this._appendTraceEvent({ type: 'frame-snapshot', snapshot });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onConsoleMessage(message: ConsoleMessage) {
|
||||||
|
const object: trace.ObjectTraceEvent = {
|
||||||
|
type: 'object',
|
||||||
|
class: 'ConsoleMessage',
|
||||||
|
guid: message.guid,
|
||||||
|
initializer: {
|
||||||
|
type: message.type(),
|
||||||
|
text: message.text(),
|
||||||
|
location: message.location(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this._appendTraceEvent(object);
|
||||||
|
|
||||||
|
const event: trace.EventTraceEvent = {
|
||||||
|
type: 'event',
|
||||||
|
class: 'BrowserContext',
|
||||||
|
method: 'console',
|
||||||
|
params: { message: { guid: message.guid } },
|
||||||
|
time: monotonicTime(),
|
||||||
|
pageId: message.page().guid,
|
||||||
|
};
|
||||||
|
this._appendTraceEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
private _startScreencastInPage(page: Page) {
|
private _startScreencastInPage(page: Page) {
|
||||||
page.setScreencastOptions(kScreencastOptions);
|
page.setScreencastOptions(kScreencastOptions);
|
||||||
const prefix = page.guid;
|
const prefix = page.guid;
|
||||||
|
|
|
||||||
|
|
@ -1684,7 +1684,7 @@ export type BrowserContextCreateTempFileResult = {
|
||||||
writableStream: WritableStreamChannel,
|
writableStream: WritableStreamChannel,
|
||||||
};
|
};
|
||||||
export type BrowserContextUpdateSubscriptionParams = {
|
export type BrowserContextUpdateSubscriptionParams = {
|
||||||
event: 'request' | 'response' | 'requestFinished' | 'requestFailed',
|
event: 'console' | 'dialog' | 'request' | 'response' | 'requestFinished' | 'requestFailed',
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
};
|
};
|
||||||
export type BrowserContextUpdateSubscriptionOptions = {
|
export type BrowserContextUpdateSubscriptionOptions = {
|
||||||
|
|
@ -2201,7 +2201,7 @@ export type PageBringToFrontParams = {};
|
||||||
export type PageBringToFrontOptions = {};
|
export type PageBringToFrontOptions = {};
|
||||||
export type PageBringToFrontResult = void;
|
export type PageBringToFrontResult = void;
|
||||||
export type PageUpdateSubscriptionParams = {
|
export type PageUpdateSubscriptionParams = {
|
||||||
event: 'fileChooser' | 'request' | 'response' | 'requestFinished' | 'requestFailed',
|
event: 'console' | 'dialog' | 'fileChooser' | 'request' | 'response' | 'requestFinished' | 'requestFailed',
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
};
|
};
|
||||||
export type PageUpdateSubscriptionOptions = {
|
export type PageUpdateSubscriptionOptions = {
|
||||||
|
|
|
||||||
|
|
@ -1153,6 +1153,8 @@ BrowserContext:
|
||||||
event:
|
event:
|
||||||
type: enum
|
type: enum
|
||||||
literals:
|
literals:
|
||||||
|
- console
|
||||||
|
- dialog
|
||||||
- request
|
- request
|
||||||
- response
|
- response
|
||||||
- requestFinished
|
- requestFinished
|
||||||
|
|
@ -1588,6 +1590,8 @@ Page:
|
||||||
event:
|
event:
|
||||||
type: enum
|
type: enum
|
||||||
literals:
|
literals:
|
||||||
|
- console
|
||||||
|
- dialog
|
||||||
- fileChooser
|
- fileChooser
|
||||||
- request
|
- request
|
||||||
- response
|
- response
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,12 @@ it('should be able to capture alert', async ({ page }) => {
|
||||||
const win = window.open('');
|
const win = window.open('');
|
||||||
win.alert('hello');
|
win.alert('hello');
|
||||||
});
|
});
|
||||||
const popup = await page.waitForEvent('popup');
|
const [popup, dialog] = await Promise.all([
|
||||||
const dialog = await popup.waitForEvent('dialog');
|
page.waitForEvent('popup'),
|
||||||
|
page.context().waitForEvent('dialog'),
|
||||||
|
]);
|
||||||
expect(dialog.message()).toBe('hello');
|
expect(dialog.message()).toBe('hello');
|
||||||
|
expect(dialog.page()).toBe(popup);
|
||||||
await dialog.dismiss();
|
await dialog.dismiss();
|
||||||
await evaluatePromise;
|
await evaluatePromise;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue