UNPKG

chrome-devtools-frontend

Version:
788 lines (697 loc) • 27.1 kB
// Copyright 2017 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 * as Protocol from '../../generated/protocol.js'; import * as Common from '../common/common.js'; import * as Platform from '../platform/platform.js'; import {CategorizedBreakpoint, Category} from './CategorizedBreakpoint.js'; import type {EventListenerPausedDetailsAuxData, Location} from './DebuggerModel.js'; import {DOMModel, type DOMNode, Events as DOMModelEvents} from './DOMModel.js'; import {RemoteObject} from './RemoteObject.js'; import {RuntimeModel} from './RuntimeModel.js'; import {SDKModel} from './SDKModel.js'; import {Capability, type Target} from './Target.js'; import {type SDKModelObserver, TargetManager} from './TargetManager.js'; export class DOMDebuggerModel extends SDKModel<EventTypes> { readonly agent: ProtocolProxyApi.DOMDebuggerApi; readonly #runtimeModel: RuntimeModel; #domModel: DOMModel; #domBreakpoints: DOMBreakpoint[]; readonly #domBreakpointsSetting: Common.Settings.Setting<Array<{ url: Platform.DevToolsPath.UrlString, path: string, type: Protocol.DOMDebugger.DOMBreakpointType, enabled: boolean, }>>; suspended = false; constructor(target: Target) { super(target); this.agent = target.domdebuggerAgent(); this.#runtimeModel = (target.model(RuntimeModel) as RuntimeModel); this.#domModel = (target.model(DOMModel) as DOMModel); this.#domModel.addEventListener(DOMModelEvents.DocumentUpdated, this.documentUpdated, this); this.#domModel.addEventListener(DOMModelEvents.NodeRemoved, this.nodeRemoved, this); this.#domBreakpoints = []; this.#domBreakpointsSetting = Common.Settings.Settings.instance().createLocalSetting('dom-breakpoints', []); if (this.#domModel.existingDocument()) { void this.documentUpdated(); } } runtimeModel(): RuntimeModel { return this.#runtimeModel; } override async suspendModel(): Promise<void> { this.suspended = true; } override async resumeModel(): Promise<void> { this.suspended = false; } async eventListeners(remoteObject: RemoteObject): Promise<EventListener[]> { console.assert(remoteObject.runtimeModel() === this.#runtimeModel); if (!remoteObject.objectId) { return []; } const listeners = await this.agent.invoke_getEventListeners({objectId: remoteObject.objectId}); const eventListeners = []; for (const payload of listeners.listeners || []) { const location = this.#runtimeModel.debuggerModel().createRawLocationByScriptId( payload.scriptId, payload.lineNumber, payload.columnNumber); if (!location) { continue; } eventListeners.push(new EventListener( this, remoteObject, payload.type, payload.useCapture, payload.passive, payload.once, payload.handler ? this.#runtimeModel.createRemoteObject(payload.handler) : null, payload.originalHandler ? this.#runtimeModel.createRemoteObject(payload.originalHandler) : null, location, null)); } return eventListeners; } retrieveDOMBreakpoints(): void { void this.#domModel.requestDocument(); } domBreakpoints(): DOMBreakpoint[] { return this.#domBreakpoints.slice(); } hasDOMBreakpoint(node: DOMNode, type: Protocol.DOMDebugger.DOMBreakpointType): boolean { return this.#domBreakpoints.some(breakpoint => (breakpoint.node === node && breakpoint.type === type)); } setDOMBreakpoint(node: DOMNode, type: Protocol.DOMDebugger.DOMBreakpointType): DOMBreakpoint { for (const breakpoint of this.#domBreakpoints) { if (breakpoint.node === node && breakpoint.type === type) { this.toggleDOMBreakpoint(breakpoint, true); return breakpoint; } } const breakpoint = new DOMBreakpoint(this, node, type, true); this.#domBreakpoints.push(breakpoint); this.enableDOMBreakpoint(breakpoint); this.saveDOMBreakpoints(); this.dispatchEventToListeners(Events.DOM_BREAKPOINT_ADDED, breakpoint); return breakpoint; } removeDOMBreakpoint(node: DOMNode, type: Protocol.DOMDebugger.DOMBreakpointType): void { this.removeDOMBreakpoints(breakpoint => breakpoint.node === node && breakpoint.type === type); } removeAllDOMBreakpoints(): void { this.removeDOMBreakpoints(_breakpoint => true); } toggleDOMBreakpoint(breakpoint: DOMBreakpoint, enabled: boolean): void { if (enabled === breakpoint.enabled) { return; } breakpoint.enabled = enabled; if (enabled) { this.enableDOMBreakpoint(breakpoint); } else { this.disableDOMBreakpoint(breakpoint); } this.saveDOMBreakpoints(); this.dispatchEventToListeners(Events.DOM_BREAKPOINT_TOGGLED, breakpoint); } private enableDOMBreakpoint(breakpoint: DOMBreakpoint): void { if (breakpoint.node.id) { void this.agent.invoke_setDOMBreakpoint({nodeId: breakpoint.node.id, type: breakpoint.type}); breakpoint.node.setMarker(Marker, true); } } private disableDOMBreakpoint(breakpoint: DOMBreakpoint): void { if (breakpoint.node.id) { void this.agent.invoke_removeDOMBreakpoint({nodeId: breakpoint.node.id, type: breakpoint.type}); breakpoint.node.setMarker(Marker, this.nodeHasBreakpoints(breakpoint.node) ? true : null); } } private nodeHasBreakpoints(node: DOMNode): boolean { for (const breakpoint of this.#domBreakpoints) { if (breakpoint.node === node && breakpoint.enabled) { return true; } } return false; } resolveDOMBreakpointData(auxData: { type: Protocol.DOMDebugger.DOMBreakpointType, nodeId: Protocol.DOM.NodeId, targetNodeId: Protocol.DOM.NodeId, insertion: boolean, }): { type: Protocol.DOMDebugger.DOMBreakpointType, node: DOMNode, targetNode: DOMNode|null, insertion: boolean, }|null { const type = auxData['type']; const node = this.#domModel.nodeForId(auxData['nodeId']); if (!type || !node) { return null; } let targetNode: (DOMNode|null)|null = null; let insertion = false; if (type === Protocol.DOMDebugger.DOMBreakpointType.SubtreeModified) { insertion = auxData['insertion'] || false; targetNode = this.#domModel.nodeForId(auxData['targetNodeId']); } return {type, node, targetNode, insertion}; } private currentURL(): Platform.DevToolsPath.UrlString { const domDocument = this.#domModel.existingDocument(); return domDocument ? domDocument.documentURL : Platform.DevToolsPath.EmptyUrlString; } private async documentUpdated(): Promise<void> { if (this.suspended) { return; } const removed = this.#domBreakpoints; this.#domBreakpoints = []; this.dispatchEventToListeners(Events.DOM_BREAKPOINTS_REMOVED, removed); // this.currentURL() is empty when the page is reloaded because the // new document has not been requested yet and the old one has been // removed. Therefore, we need to request the document and wait for it. // Note that requestDocument() caches the document so that it is requested // only once. const document = await this.#domModel.requestDocument(); const currentURL = document ? document.documentURL : Platform.DevToolsPath.EmptyUrlString; for (const breakpoint of this.#domBreakpointsSetting.get()) { if (breakpoint.url === currentURL) { void this.#domModel.pushNodeByPathToFrontend(breakpoint.path).then(appendBreakpoint.bind(this, breakpoint)); } } function appendBreakpoint( this: DOMDebuggerModel, breakpoint: { type: Protocol.DOMDebugger.DOMBreakpointType, enabled: boolean, }, nodeId: Protocol.DOM.NodeId|null): void { const node = nodeId ? this.#domModel.nodeForId(nodeId) : null; if (!node) { return; } // Before creating a new DOMBreakpoint, we need to ensure there's no // existing breakpoint with the same node and breakpoint type, else we would create // multiple DOMBreakpoints of the same type and for the same node. for (const existingBreakpoint of this.#domBreakpoints) { if (existingBreakpoint.node === node && existingBreakpoint.type === breakpoint.type) { return; } } const domBreakpoint = new DOMBreakpoint(this, node, breakpoint.type, breakpoint.enabled); this.#domBreakpoints.push(domBreakpoint); if (breakpoint.enabled) { this.enableDOMBreakpoint(domBreakpoint); } this.dispatchEventToListeners(Events.DOM_BREAKPOINT_ADDED, domBreakpoint); } } private removeDOMBreakpoints(filter: (arg0: DOMBreakpoint) => boolean): void { const removed = []; const left = []; for (const breakpoint of this.#domBreakpoints) { if (filter(breakpoint)) { removed.push(breakpoint); if (breakpoint.enabled) { breakpoint.enabled = false; this.disableDOMBreakpoint(breakpoint); } } else { left.push(breakpoint); } } if (!removed.length) { return; } this.#domBreakpoints = left; this.saveDOMBreakpoints(); this.dispatchEventToListeners(Events.DOM_BREAKPOINTS_REMOVED, removed); } private nodeRemoved(event: Common.EventTarget.EventTargetEvent<{node: DOMNode, parent: DOMNode}>): void { if (this.suspended) { return; } const {node} = event.data; const children = node.children() || []; this.removeDOMBreakpoints(breakpoint => breakpoint.node === node || children.indexOf(breakpoint.node) !== -1); } private saveDOMBreakpoints(): void { const currentURL = this.currentURL(); const breakpoints = this.#domBreakpointsSetting.get().filter((breakpoint: { url: Platform.DevToolsPath.UrlString, }) => breakpoint.url !== currentURL); for (const breakpoint of this.#domBreakpoints) { breakpoints.push( {url: currentURL, path: breakpoint.node.path(), type: breakpoint.type, enabled: breakpoint.enabled}); } this.#domBreakpointsSetting.set(breakpoints); } } export const enum Events { DOM_BREAKPOINT_ADDED = 'DOMBreakpointAdded', DOM_BREAKPOINT_TOGGLED = 'DOMBreakpointToggled', DOM_BREAKPOINTS_REMOVED = 'DOMBreakpointsRemoved', } export interface EventTypes { [Events.DOM_BREAKPOINT_ADDED]: DOMBreakpoint; [Events.DOM_BREAKPOINT_TOGGLED]: DOMBreakpoint; [Events.DOM_BREAKPOINTS_REMOVED]: DOMBreakpoint[]; } const Marker = 'breakpoint-marker'; export class DOMBreakpoint { domDebuggerModel: DOMDebuggerModel; node: DOMNode; type: Protocol.DOMDebugger.DOMBreakpointType; enabled: boolean; constructor( domDebuggerModel: DOMDebuggerModel, node: DOMNode, type: Protocol.DOMDebugger.DOMBreakpointType, enabled: boolean) { this.domDebuggerModel = domDebuggerModel; this.node = node; this.type = type; this.enabled = enabled; } } export class EventListener { readonly #domDebuggerModel: DOMDebuggerModel; readonly #eventTarget: RemoteObject; readonly #type: string; readonly #useCapture: boolean; readonly #passive: boolean; readonly #once: boolean; readonly #handler: RemoteObject|null; readonly #originalHandler: RemoteObject|null; readonly #location: Location; readonly #sourceURL: Platform.DevToolsPath.UrlString; readonly #customRemoveFunction: RemoteObject|null; #origin: string; constructor( domDebuggerModel: DOMDebuggerModel, eventTarget: RemoteObject, type: string, useCapture: boolean, passive: boolean, once: boolean, handler: RemoteObject|null, originalHandler: RemoteObject|null, location: Location, customRemoveFunction: RemoteObject|null, origin?: string) { this.#domDebuggerModel = domDebuggerModel; this.#eventTarget = eventTarget; this.#type = type; this.#useCapture = useCapture; this.#passive = passive; this.#once = once; this.#handler = handler; this.#originalHandler = originalHandler || handler; this.#location = location; const script = location.script(); this.#sourceURL = script ? script.contentURL() : Platform.DevToolsPath.EmptyUrlString; this.#customRemoveFunction = customRemoveFunction; this.#origin = origin || EventListener.Origin.RAW; } domDebuggerModel(): DOMDebuggerModel { return this.#domDebuggerModel; } type(): string { return this.#type; } useCapture(): boolean { return this.#useCapture; } passive(): boolean { return this.#passive; } once(): boolean { return this.#once; } handler(): RemoteObject|null { return this.#handler; } location(): Location { return this.#location; } sourceURL(): Platform.DevToolsPath.UrlString { return this.#sourceURL; } originalHandler(): RemoteObject|null { return this.#originalHandler; } canRemove(): boolean { return Boolean(this.#customRemoveFunction) || this.#origin !== EventListener.Origin.FRAMEWORK_USER; } remove(): Promise<void> { if (!this.canRemove()) { return Promise.resolve(undefined); } if (this.#origin !== EventListener.Origin.FRAMEWORK_USER) { function removeListener( this: { removeEventListener: (arg0: string, arg1: () => void, arg2: boolean) => void, }, type: string, listener: () => void, useCapture: boolean): void { this.removeEventListener(type, listener, useCapture); // @ts-expect-error: if (this['on' + type]) { // @ts-expect-error: this['on' + type] = undefined; } } return this.#eventTarget .callFunction( removeListener, [ RemoteObject.toCallArgument(this.#type), RemoteObject.toCallArgument(this.#originalHandler), RemoteObject.toCallArgument(this.#useCapture), ]) .then(() => undefined); } if (this.#customRemoveFunction) { function callCustomRemove( this: (arg0: string, arg1: () => void, arg2: boolean, arg3: boolean) => void, type: string, listener: () => void, useCapture: boolean, passive: boolean): void { this.call(null, type, listener, useCapture, passive); } return this.#customRemoveFunction .callFunction( callCustomRemove, [ RemoteObject.toCallArgument(this.#type), RemoteObject.toCallArgument(this.#originalHandler), RemoteObject.toCallArgument(this.#useCapture), RemoteObject.toCallArgument(this.#passive), ]) .then(() => undefined); } return Promise.resolve(undefined); } canTogglePassive(): boolean { return this.#origin !== EventListener.Origin.FRAMEWORK_USER; } togglePassive(): Promise<undefined> { return this.#eventTarget .callFunction( callTogglePassive, [ RemoteObject.toCallArgument(this.#type), RemoteObject.toCallArgument(this.#originalHandler), RemoteObject.toCallArgument(this.#useCapture), RemoteObject.toCallArgument(this.#passive), ]) .then(() => undefined); function callTogglePassive( this: { addEventListener: (arg0: string, arg1: () => void, arg2: { capture: boolean, passive: boolean, }) => void, removeEventListener: (arg0: string, arg1: () => void, arg2: { capture: boolean, }) => void, }, type: string, listener: () => void, useCapture: boolean, passive: boolean): void { this.removeEventListener(type, listener, {capture: useCapture}); this.addEventListener(type, listener, {capture: useCapture, passive: !passive}); } } origin(): string { return this.#origin; } markAsFramework(): void { this.#origin = EventListener.Origin.FRAMEWORK; } isScrollBlockingType(): boolean { return this.#type === 'touchstart' || this.#type === 'touchmove' || this.#type === 'mousewheel' || this.#type === 'wheel'; } } export namespace EventListener { export const enum Origin { RAW = 'Raw', FRAMEWORK = 'Framework', FRAMEWORK_USER = 'FrameworkUser', } } export class CSPViolationBreakpoint extends CategorizedBreakpoint { readonly #type: Protocol.DOMDebugger.CSPViolationType; constructor(category: Category, type: Protocol.DOMDebugger.CSPViolationType) { super(category, type); this.#type = type; } type(): Protocol.DOMDebugger.CSPViolationType { return this.#type; } } export class DOMEventListenerBreakpoint extends CategorizedBreakpoint { readonly eventTargetNames: string[]; constructor(eventName: string, eventTargetNames: string[], category: Category) { super(category, eventName); this.eventTargetNames = eventTargetNames; } override setEnabled(enabled: boolean): void { if (this.enabled() === enabled) { return; } super.setEnabled(enabled); for (const model of TargetManager.instance().models(DOMDebuggerModel)) { this.updateOnModel(model); } } updateOnModel(model: DOMDebuggerModel): void { for (const eventTargetName of this.eventTargetNames) { if (this.enabled()) { void model.agent.invoke_setEventListenerBreakpoint({eventName: this.name, targetName: eventTargetName}); } else { void model.agent.invoke_removeEventListenerBreakpoint({eventName: this.name, targetName: eventTargetName}); } } } static readonly listener = 'listener:'; } let domDebuggerManagerInstance: DOMDebuggerManager; export class DOMDebuggerManager implements SDKModelObserver<DOMDebuggerModel> { readonly #xhrBreakpointsSetting: Common.Settings.Setting<Array<{url: string, enabled: boolean}>>; readonly #xhrBreakpoints = new Map<string, boolean>(); readonly #cspViolationsToBreakOn: CSPViolationBreakpoint[] = []; readonly #eventListenerBreakpoints: DOMEventListenerBreakpoint[] = []; constructor() { this.#xhrBreakpointsSetting = Common.Settings.Settings.instance().createLocalSetting('xhr-breakpoints', []); for (const breakpoint of this.#xhrBreakpointsSetting.get()) { this.#xhrBreakpoints.set(breakpoint.url, breakpoint.enabled); } this.#cspViolationsToBreakOn.push(new CSPViolationBreakpoint( Category.TRUSTED_TYPE_VIOLATION, Protocol.DOMDebugger.CSPViolationType.TrustedtypeSinkViolation)); this.#cspViolationsToBreakOn.push(new CSPViolationBreakpoint( Category.TRUSTED_TYPE_VIOLATION, Protocol.DOMDebugger.CSPViolationType.TrustedtypePolicyViolation)); this.createEventListenerBreakpoints( Category.MEDIA, [ 'play', 'pause', 'playing', 'canplay', 'canplaythrough', 'seeking', 'seeked', 'timeupdate', 'ended', 'ratechange', 'durationchange', 'volumechange', 'loadstart', 'progress', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'waiting', ], ['audio', 'video']); this.createEventListenerBreakpoints( Category.PICTURE_IN_PICTURE, ['enterpictureinpicture', 'leavepictureinpicture'], ['video']); this.createEventListenerBreakpoints(Category.PICTURE_IN_PICTURE, ['resize'], ['PictureInPictureWindow']); this.createEventListenerBreakpoints(Category.PICTURE_IN_PICTURE, ['enter'], ['documentPictureInPicture']); this.createEventListenerBreakpoints( Category.CLIPBOARD, ['copy', 'cut', 'paste', 'beforecopy', 'beforecut', 'beforepaste'], ['*']); this.createEventListenerBreakpoints( Category.CONTROL, [ 'resize', 'scroll', 'scrollend', 'scrollsnapchange', 'scrollsnapchanging', 'zoom', 'focus', 'blur', 'select', 'change', 'submit', 'reset', ], ['*']); this.createEventListenerBreakpoints(Category.DEVICE, ['deviceorientation', 'devicemotion'], ['*']); this.createEventListenerBreakpoints( Category.DOM_MUTATION, [ 'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'DOMAttrModified', 'DOMCharacterDataModified', 'DOMNodeInserted', 'DOMNodeInsertedIntoDocument', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument', 'DOMSubtreeModified', 'DOMContentLoaded', ], ['*']); this.createEventListenerBreakpoints( Category.DRAG_DROP, ['drag', 'dragstart', 'dragend', 'dragenter', 'dragover', 'dragleave', 'drop'], ['*']); this.createEventListenerBreakpoints(Category.KEYBOARD, ['keydown', 'keyup', 'keypress', 'input'], ['*']); this.createEventListenerBreakpoints( Category.LOAD, [ 'load', 'beforeunload', 'unload', 'abort', 'error', 'hashchange', 'popstate', 'navigate', 'navigatesuccess', 'navigateerror', 'currentchange', 'navigateto', 'navigatefrom', 'finish', 'dispose', ], ['*']); this.createEventListenerBreakpoints( Category.MOUSE, [ 'auxclick', 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout', 'mouseenter', 'mouseleave', 'mousewheel', 'wheel', 'contextmenu', ], ['*']); this.createEventListenerBreakpoints( Category.POINTER, [ 'pointerover', 'pointerout', 'pointerenter', 'pointerleave', 'pointerdown', 'pointerup', 'pointermove', 'pointercancel', 'gotpointercapture', 'lostpointercapture', 'pointerrawupdate', ], ['*']); this.createEventListenerBreakpoints(Category.TOUCH, ['touchstart', 'touchmove', 'touchend', 'touchcancel'], ['*']); this.createEventListenerBreakpoints(Category.WORKER, ['message', 'messageerror'], ['*']); this.createEventListenerBreakpoints( Category.XHR, ['readystatechange', 'load', 'loadstart', 'loadend', 'abort', 'error', 'progress', 'timeout'], ['xmlhttprequest', 'xmlhttprequestupload']); TargetManager.instance().observeModels(DOMDebuggerModel, this); } static instance(opts: { forceNew: boolean|null, } = {forceNew: null}): DOMDebuggerManager { const {forceNew} = opts; if (!domDebuggerManagerInstance || forceNew) { domDebuggerManagerInstance = new DOMDebuggerManager(); } return domDebuggerManagerInstance; } cspViolationBreakpoints(): CSPViolationBreakpoint[] { return this.#cspViolationsToBreakOn.slice(); } private createEventListenerBreakpoints(category: Category, eventNames: string[], eventTargetNames: string[]): void { for (const eventName of eventNames) { this.#eventListenerBreakpoints.push(new DOMEventListenerBreakpoint(eventName, eventTargetNames, category)); } } resolveEventListenerBreakpoint({eventName, targetName}: EventListenerPausedDetailsAuxData): DOMEventListenerBreakpoint |null { const listenerPrefix = 'listener:'; if (eventName.startsWith(listenerPrefix)) { eventName = eventName.substring(listenerPrefix.length); } else { return null; } targetName = (targetName || '*').toLowerCase(); let result: DOMEventListenerBreakpoint|null = null; for (const breakpoint of this.#eventListenerBreakpoints) { if (eventName && breakpoint.name === eventName && breakpoint.eventTargetNames.indexOf(targetName) !== -1) { result = breakpoint; } if (!result && eventName && breakpoint.name === eventName && breakpoint.eventTargetNames.indexOf('*') !== -1) { result = breakpoint; } } return result; } eventListenerBreakpoints(): DOMEventListenerBreakpoint[] { return this.#eventListenerBreakpoints.slice(); } updateCSPViolationBreakpoints(): void { const violationTypes = this.#cspViolationsToBreakOn.filter(v => v.enabled()).map(v => v.type()); for (const model of TargetManager.instance().models(DOMDebuggerModel)) { this.updateCSPViolationBreakpointsForModel(model, violationTypes); } } private updateCSPViolationBreakpointsForModel( model: DOMDebuggerModel, violationTypes: Protocol.DOMDebugger.CSPViolationType[]): void { void model.agent.invoke_setBreakOnCSPViolation({violationTypes}); } xhrBreakpoints(): Map<string, boolean> { return this.#xhrBreakpoints; } private saveXHRBreakpoints(): void { const breakpoints = []; for (const url of this.#xhrBreakpoints.keys()) { breakpoints.push({url, enabled: this.#xhrBreakpoints.get(url) || false}); } this.#xhrBreakpointsSetting.set(breakpoints); } addXHRBreakpoint(url: string, enabled: boolean): void { this.#xhrBreakpoints.set(url, enabled); if (enabled) { for (const model of TargetManager.instance().models(DOMDebuggerModel)) { void model.agent.invoke_setXHRBreakpoint({url}); } } this.saveXHRBreakpoints(); } removeXHRBreakpoint(url: string): void { const enabled = this.#xhrBreakpoints.get(url); this.#xhrBreakpoints.delete(url); if (enabled) { for (const model of TargetManager.instance().models(DOMDebuggerModel)) { void model.agent.invoke_removeXHRBreakpoint({url}); } } this.saveXHRBreakpoints(); } toggleXHRBreakpoint(url: string, enabled: boolean): void { this.#xhrBreakpoints.set(url, enabled); for (const model of TargetManager.instance().models(DOMDebuggerModel)) { if (enabled) { void model.agent.invoke_setXHRBreakpoint({url}); } else { void model.agent.invoke_removeXHRBreakpoint({url}); } } this.saveXHRBreakpoints(); } modelAdded(domDebuggerModel: DOMDebuggerModel): void { for (const url of this.#xhrBreakpoints.keys()) { if (this.#xhrBreakpoints.get(url)) { void domDebuggerModel.agent.invoke_setXHRBreakpoint({url}); } } for (const breakpoint of this.#eventListenerBreakpoints) { if (breakpoint.enabled()) { breakpoint.updateOnModel(domDebuggerModel); } } const violationTypes = this.#cspViolationsToBreakOn.filter(v => v.enabled()).map(v => v.type()); this.updateCSPViolationBreakpointsForModel(domDebuggerModel, violationTypes); } modelRemoved(_domDebuggerModel: DOMDebuggerModel): void { } } SDKModel.register(DOMDebuggerModel, {capabilities: Capability.DOM, autostart: false});