chrome-devtools-frontend
Version:
Chrome DevTools UI
182 lines (160 loc) • 7.41 kB
text/typescript
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
import {CategorizedBreakpoint, Category} from './CategorizedBreakpoint.js';
import type {EventListenerPausedDetailsAuxData} from './DebuggerModel.js';
import {SDKModel} from './SDKModel.js';
import {Capability, type Target} from './Target.js';
import {type SDKModelObserver, TargetManager} from './TargetManager.js';
export const enum InstrumentationNames {
BEFORE_BIDDER_WORKLET_BIDDING_START = 'beforeBidderWorkletBiddingStart',
BEFORE_BIDDER_WORKLET_REPORTING_START = 'beforeBidderWorkletReportingStart',
BEFORE_SELLER_WORKLET_SCORING_START = 'beforeSellerWorkletScoringStart',
BEFORE_SELLER_WORKLET_REPORTING_START = 'beforeSellerWorkletReportingStart',
SET_TIMEOUT = 'setTimeout',
CLEAR_TIMEOUT = 'clearTimeout',
SET_TIMEOUT_CALLBACK = 'setTimeout.callback',
SET_INTERVAL = 'setInterval',
CLEAR_INTERVAL = 'clearInterval',
SET_INTERVAL_CALLBACK = 'setInterval.callback',
SCRIPT_FIRST_STATEMENT = 'scriptFirstStatement',
SCRIPT_BLOCKED_BY_CSP = 'scriptBlockedByCSP',
SHARED_STORAGE_WORKLET_SCRIPT_FIRST_STATEMENT = 'sharedStorageWorkletScriptFirstStatement',
REQUEST_ANIMATION_FRAME = 'requestAnimationFrame',
CANCEL_ANIMATION_FRAME = 'cancelAnimationFrame',
REQUEST_ANIMATION_FRAME_CALLBACK = 'requestAnimationFrame.callback',
WEBGL_ERROR_FIRED = 'webglErrorFired',
WEBGL_WARNING_FIRED = 'webglWarningFired',
ELEMENT_SET_INNER_HTML = 'Element.setInnerHTML',
CANVAS_CONTEXT_CREATED = 'canvasContextCreated',
GEOLOCATION_GET_CURRENT_POSITION = 'Geolocation.getCurrentPosition',
GEOLOCATION_WATCH_POSITION = 'Geolocation.watchPosition',
NOTIFICATION_REQUEST_PERMISSION = 'Notification.requestPermission',
DOM_WINDOW_CLOSE = 'DOMWindow.close',
DOCUMENT_WRITE = 'Document.write',
AUDIO_CONTEXT_CREATED = 'audioContextCreated',
AUDIO_CONTEXT_CLOSED = 'audioContextClosed',
AUDIO_CONTEXT_RESUMED = 'audioContextResumed',
AUDIO_CONTEXT_SUSPENDED = 'audioContextSuspended',
}
export class EventBreakpointsModel extends SDKModel<void> {
readonly agent: ProtocolProxyApi.EventBreakpointsApi;
constructor(target: Target) {
super(target);
this.agent = target.eventBreakpointsAgent();
}
}
// This implementation (as opposed to similar class in DOMDebuggerModel) is for
// instrumentation breakpoints in targets that run JS but do not have a DOM.
class EventListenerBreakpoint extends CategorizedBreakpoint {
override setEnabled(enabled: boolean): void {
if (this.enabled() === enabled) {
return;
}
super.setEnabled(enabled);
for (const model of TargetManager.instance().models(EventBreakpointsModel)) {
this.updateOnModel(model);
}
}
updateOnModel(model: EventBreakpointsModel): void {
if (this.enabled()) {
void model.agent.invoke_setInstrumentationBreakpoint({eventName: this.name});
} else {
void model.agent.invoke_removeInstrumentationBreakpoint({eventName: this.name});
}
}
static readonly instrumentationPrefix = 'instrumentation:';
}
let eventBreakpointManagerInstance: EventBreakpointsManager;
export class EventBreakpointsManager implements SDKModelObserver<EventBreakpointsModel> {
readonly #eventListenerBreakpoints: EventListenerBreakpoint[] = [];
constructor() {
this.createInstrumentationBreakpoints(Category.AUCTION_WORKLET, [
InstrumentationNames.BEFORE_BIDDER_WORKLET_BIDDING_START,
InstrumentationNames.BEFORE_BIDDER_WORKLET_REPORTING_START,
InstrumentationNames.BEFORE_SELLER_WORKLET_SCORING_START,
InstrumentationNames.BEFORE_SELLER_WORKLET_REPORTING_START,
]);
this.createInstrumentationBreakpoints(Category.ANIMATION, [
InstrumentationNames.REQUEST_ANIMATION_FRAME,
InstrumentationNames.CANCEL_ANIMATION_FRAME,
InstrumentationNames.REQUEST_ANIMATION_FRAME_CALLBACK,
]);
this.createInstrumentationBreakpoints(Category.CANVAS, [
InstrumentationNames.CANVAS_CONTEXT_CREATED,
InstrumentationNames.WEBGL_ERROR_FIRED,
InstrumentationNames.WEBGL_WARNING_FIRED,
]);
this.createInstrumentationBreakpoints(Category.GEOLOCATION, [
InstrumentationNames.GEOLOCATION_GET_CURRENT_POSITION,
InstrumentationNames.GEOLOCATION_WATCH_POSITION,
]);
this.createInstrumentationBreakpoints(Category.NOTIFICATION, [
InstrumentationNames.NOTIFICATION_REQUEST_PERMISSION,
]);
this.createInstrumentationBreakpoints(Category.PARSE, [
InstrumentationNames.ELEMENT_SET_INNER_HTML,
InstrumentationNames.DOCUMENT_WRITE,
]);
this.createInstrumentationBreakpoints(Category.SCRIPT, [
InstrumentationNames.SCRIPT_FIRST_STATEMENT,
InstrumentationNames.SCRIPT_BLOCKED_BY_CSP,
]);
this.createInstrumentationBreakpoints(Category.SHARED_STORAGE_WORKLET, [
InstrumentationNames.SHARED_STORAGE_WORKLET_SCRIPT_FIRST_STATEMENT,
]);
this.createInstrumentationBreakpoints(Category.TIMER, [
InstrumentationNames.SET_TIMEOUT,
InstrumentationNames.CLEAR_TIMEOUT,
InstrumentationNames.SET_TIMEOUT_CALLBACK,
InstrumentationNames.SET_INTERVAL,
InstrumentationNames.CLEAR_INTERVAL,
InstrumentationNames.SET_INTERVAL_CALLBACK,
]);
this.createInstrumentationBreakpoints(Category.WINDOW, [
InstrumentationNames.DOM_WINDOW_CLOSE,
]);
this.createInstrumentationBreakpoints(Category.WEB_AUDIO, [
InstrumentationNames.AUDIO_CONTEXT_CREATED,
InstrumentationNames.AUDIO_CONTEXT_CLOSED,
InstrumentationNames.AUDIO_CONTEXT_RESUMED,
InstrumentationNames.AUDIO_CONTEXT_SUSPENDED,
]);
TargetManager.instance().observeModels(EventBreakpointsModel, this);
}
static instance(opts: {
forceNew: boolean|null,
} = {forceNew: null}): EventBreakpointsManager {
const {forceNew} = opts;
if (!eventBreakpointManagerInstance || forceNew) {
eventBreakpointManagerInstance = new EventBreakpointsManager();
}
return eventBreakpointManagerInstance;
}
private createInstrumentationBreakpoints(category: Category, instrumentationNames: InstrumentationNames[]): void {
for (const instrumentationName of instrumentationNames) {
this.#eventListenerBreakpoints.push(new EventListenerBreakpoint(category, instrumentationName));
}
}
eventListenerBreakpoints(): EventListenerBreakpoint[] {
return this.#eventListenerBreakpoints.slice();
}
resolveEventListenerBreakpoint({eventName}: EventListenerPausedDetailsAuxData): EventListenerBreakpoint|null {
if (!eventName.startsWith(EventListenerBreakpoint.instrumentationPrefix)) {
return null;
}
const instrumentationName = eventName.substring(EventListenerBreakpoint.instrumentationPrefix.length);
return this.#eventListenerBreakpoints.find(b => b.name === instrumentationName) || null;
}
modelAdded(eventBreakpointModel: EventBreakpointsModel): void {
for (const breakpoint of this.#eventListenerBreakpoints) {
if (breakpoint.enabled()) {
breakpoint.updateOnModel(eventBreakpointModel);
}
}
}
modelRemoved(_eventBreakpointModel: EventBreakpointsModel): void {
}
}
SDKModel.register(EventBreakpointsModel, {capabilities: Capability.EVENT_BREAKPOINTS, autostart: false});