| // Copyright 2017 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| export default class DOMDebuggerModel extends SDK.SDKModel { | |
| /** | |
| * @param {!SDK.Target} target | |
| */ | |
| constructor(target) { | |
| super(target); | |
| this._agent = target.domdebuggerAgent(); | |
| this._runtimeModel = /** @type {!SDK.RuntimeModel} */ (target.model(SDK.RuntimeModel)); | |
| this._domModel = /** @type {!SDK.DOMModel} */ (target.model(SDK.DOMModel)); | |
| this._domModel.addEventListener(SDK.DOMModel.Events.DocumentUpdated, this._documentUpdated, this); | |
| this._domModel.addEventListener(SDK.DOMModel.Events.NodeRemoved, this._nodeRemoved, this); | |
| /** @type {!Array<!DOMBreakpoint>} */ | |
| this._domBreakpoints = []; | |
| this._domBreakpointsSetting = Common.settings.createLocalSetting('domBreakpoints', []); | |
| if (this._domModel.existingDocument()) { | |
| this._documentUpdated(); | |
| } | |
| } | |
| /** | |
| * @return {!SDK.RuntimeModel} | |
| */ | |
| runtimeModel() { | |
| return this._runtimeModel; | |
| } | |
| /** | |
| * @param {!SDK.RemoteObject} remoteObject | |
| * @return {!Promise<!Array<!SDK.EventListener>>} | |
| */ | |
| async eventListeners(remoteObject) { | |
| console.assert(remoteObject.runtimeModel() === this._runtimeModel); | |
| if (!remoteObject.objectId) { | |
| return []; | |
| } | |
| const payloads = await this._agent.getEventListeners(/** @type {string} */ (remoteObject.objectId)); | |
| const eventListeners = []; | |
| for (const payload of payloads || []) { | |
| const location = this._runtimeModel.debuggerModel().createRawLocationByScriptId( | |
| payload.scriptId, payload.lineNumber, payload.columnNumber); | |
| if (!location) { | |
| continue; | |
| } | |
| eventListeners.push(new SDK.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() { | |
| this._domModel.requestDocument(); | |
| } | |
| /** | |
| * @return {!Array<!DOMBreakpoint>} | |
| */ | |
| domBreakpoints() { | |
| return this._domBreakpoints.slice(); | |
| } | |
| /** | |
| * @param {!SDK.DOMNode} node | |
| * @param {!SDK.DOMDebuggerModel.DOMBreakpoint.Type} type | |
| * @return {boolean} | |
| */ | |
| hasDOMBreakpoint(node, type) { | |
| return this._domBreakpoints.some(breakpoint => (breakpoint.node === node && breakpoint.type === type)); | |
| } | |
| /** | |
| * @param {!SDK.DOMNode} node | |
| * @param {!SDK.DOMDebuggerModel.DOMBreakpoint.Type} type | |
| * @return {!DOMBreakpoint} | |
| */ | |
| setDOMBreakpoint(node, type) { | |
| 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._saveDOMBreakpoints(); | |
| this._enableDOMBreakpoint(breakpoint); | |
| this.dispatchEventToListeners(Events.DOMBreakpointAdded, breakpoint); | |
| return breakpoint; | |
| } | |
| /** | |
| * @param {!SDK.DOMNode} node | |
| * @param {!SDK.DOMDebuggerModel.DOMBreakpoint.Type} type | |
| */ | |
| removeDOMBreakpoint(node, type) { | |
| this._removeDOMBreakpoints(breakpoint => breakpoint.node === node && breakpoint.type === type); | |
| } | |
| removeAllDOMBreakpoints() { | |
| this._removeDOMBreakpoints(breakpoint => true); | |
| } | |
| /** | |
| * @param {!DOMBreakpoint} breakpoint | |
| * @param {boolean} enabled | |
| */ | |
| toggleDOMBreakpoint(breakpoint, enabled) { | |
| if (enabled === breakpoint.enabled) { | |
| return; | |
| } | |
| breakpoint.enabled = enabled; | |
| if (enabled) { | |
| this._enableDOMBreakpoint(breakpoint); | |
| } else { | |
| this._disableDOMBreakpoint(breakpoint); | |
| } | |
| this.dispatchEventToListeners(Events.DOMBreakpointToggled, breakpoint); | |
| } | |
| /** | |
| * @param {!DOMBreakpoint} breakpoint | |
| */ | |
| _enableDOMBreakpoint(breakpoint) { | |
| this._agent.setDOMBreakpoint(breakpoint.node.id, breakpoint.type); | |
| breakpoint.node.setMarker(Marker, true); | |
| } | |
| /** | |
| * @param {!DOMBreakpoint} breakpoint | |
| */ | |
| _disableDOMBreakpoint(breakpoint) { | |
| this._agent.removeDOMBreakpoint(breakpoint.node.id, breakpoint.type); | |
| breakpoint.node.setMarker(Marker, this._nodeHasBreakpoints(breakpoint.node) ? true : null); | |
| } | |
| /** | |
| * @param {!SDK.DOMNode} node | |
| * @return {boolean} | |
| */ | |
| _nodeHasBreakpoints(node) { | |
| for (const breakpoint of this._domBreakpoints) { | |
| if (breakpoint.node === node && breakpoint.enabled) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * @param {!Object} auxData | |
| * @return {?{type: !SDK.DOMDebuggerModel.DOMBreakpoint.Type, node: !SDK.DOMNode, targetNode: ?SDK.DOMNode, insertion: boolean}} | |
| */ | |
| resolveDOMBreakpointData(auxData) { | |
| const type = auxData['type']; | |
| const node = this._domModel.nodeForId(auxData['nodeId']); | |
| if (!type || !node) { | |
| return null; | |
| } | |
| let targetNode = null; | |
| let insertion = false; | |
| if (type === SDK.DOMDebuggerModel.DOMBreakpoint.Type.SubtreeModified) { | |
| insertion = auxData['insertion'] || false; | |
| targetNode = this._domModel.nodeForId(auxData['targetNodeId']); | |
| } | |
| return {type: type, node: node, targetNode: targetNode, insertion: insertion}; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| _currentURL() { | |
| const domDocument = this._domModel.existingDocument(); | |
| return domDocument ? domDocument.documentURL : ''; | |
| } | |
| _documentUpdated() { | |
| const removed = this._domBreakpoints; | |
| this._domBreakpoints = []; | |
| this.dispatchEventToListeners(Events.DOMBreakpointsRemoved, removed); | |
| const currentURL = this._currentURL(); | |
| for (const breakpoint of this._domBreakpointsSetting.get()) { | |
| if (breakpoint.url === currentURL) { | |
| this._domModel.pushNodeByPathToFrontend(breakpoint.path).then(appendBreakpoint.bind(this, breakpoint)); | |
| } | |
| } | |
| /** | |
| * @param {!{type: !SDK.DOMDebuggerModel.DOMBreakpoint.Type, enabled: boolean}} breakpoint | |
| * @param {?number} nodeId | |
| * @this {DOMDebuggerModel} | |
| */ | |
| function appendBreakpoint(breakpoint, nodeId) { | |
| const node = nodeId ? this._domModel.nodeForId(nodeId) : null; | |
| if (!node) { | |
| return; | |
| } | |
| const domBreakpoint = new DOMBreakpoint(this, node, breakpoint.type, breakpoint.enabled); | |
| this._domBreakpoints.push(domBreakpoint); | |
| if (breakpoint.enabled) { | |
| this._enableDOMBreakpoint(domBreakpoint); | |
| } | |
| this.dispatchEventToListeners(Events.DOMBreakpointAdded, domBreakpoint); | |
| } | |
| } | |
| /** | |
| * @param {function(!DOMBreakpoint):boolean} filter | |
| */ | |
| _removeDOMBreakpoints(filter) { | |
| 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.DOMBreakpointsRemoved, removed); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _nodeRemoved(event) { | |
| const node = /** @type {!SDK.DOMNode} */ (event.data.node); | |
| const children = node.children() || []; | |
| this._removeDOMBreakpoints(breakpoint => breakpoint.node === node || children.indexOf(breakpoint.node) !== -1); | |
| } | |
| _saveDOMBreakpoints() { | |
| const currentURL = this._currentURL(); | |
| const breakpoints = this._domBreakpointsSetting.get().filter(breakpoint => 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); | |
| } | |
| } | |
| /** @enum {symbol} */ | |
| export const Events = { | |
| DOMBreakpointAdded: Symbol('DOMBreakpointAdded'), | |
| DOMBreakpointToggled: Symbol('DOMBreakpointToggled'), | |
| DOMBreakpointsRemoved: Symbol('DOMBreakpointsRemoved'), | |
| }; | |
| const Marker = 'breakpoint-marker'; | |
| export class DOMBreakpoint { | |
| /** | |
| * @param {!DOMDebuggerModel} domDebuggerModel | |
| * @param {!SDK.DOMNode} node | |
| * @param {!SDK.DOMDebuggerModel.DOMBreakpoint.Type} type | |
| * @param {boolean} enabled | |
| */ | |
| constructor(domDebuggerModel, node, type, enabled) { | |
| this.domDebuggerModel = domDebuggerModel; | |
| this.node = node; | |
| this.type = type; | |
| this.enabled = enabled; | |
| } | |
| } | |
| export class EventListener { | |
| /** | |
| * @param {!DOMDebuggerModel} domDebuggerModel | |
| * @param {!SDK.RemoteObject} eventTarget | |
| * @param {string} type | |
| * @param {boolean} useCapture | |
| * @param {boolean} passive | |
| * @param {boolean} once | |
| * @param {?SDK.RemoteObject} handler | |
| * @param {?SDK.RemoteObject} originalHandler | |
| * @param {!SDK.DebuggerModel.Location} location | |
| * @param {?SDK.RemoteObject} customRemoveFunction | |
| * @param {!EventListener.Origin=} origin | |
| */ | |
| constructor( | |
| domDebuggerModel, eventTarget, type, useCapture, passive, once, handler, originalHandler, location, | |
| customRemoveFunction, origin) { | |
| 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() : ''; | |
| this._customRemoveFunction = customRemoveFunction; | |
| this._origin = origin || EventListener.Origin.Raw; | |
| } | |
| /** | |
| * @return {!DOMDebuggerModel} | |
| */ | |
| domDebuggerModel() { | |
| return this._domDebuggerModel; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| type() { | |
| return this._type; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| useCapture() { | |
| return this._useCapture; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| passive() { | |
| return this._passive; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| once() { | |
| return this._once; | |
| } | |
| /** | |
| * @return {?SDK.RemoteObject} | |
| */ | |
| handler() { | |
| return this._handler; | |
| } | |
| /** | |
| * @return {!SDK.DebuggerModel.Location} | |
| */ | |
| location() { | |
| return this._location; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| sourceURL() { | |
| return this._sourceURL; | |
| } | |
| /** | |
| * @return {?SDK.RemoteObject} | |
| */ | |
| originalHandler() { | |
| return this._originalHandler; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| canRemove() { | |
| return !!this._customRemoveFunction || this._origin !== EventListener.Origin.FrameworkUser; | |
| } | |
| /** | |
| * @return {!Promise<undefined>} | |
| */ | |
| remove() { | |
| if (!this.canRemove()) { | |
| return Promise.resolve(); | |
| } | |
| if (this._origin !== EventListener.Origin.FrameworkUser) { | |
| /** | |
| * @param {string} type | |
| * @param {function()} listener | |
| * @param {boolean} useCapture | |
| * @this {Object} | |
| * @suppressReceiverCheck | |
| */ | |
| function removeListener(type, listener, useCapture) { | |
| this.removeEventListener(type, listener, useCapture); | |
| if (this['on' + type]) { | |
| this['on' + type] = undefined; | |
| } | |
| } | |
| return /** @type {!Promise<undefined>} */ (this._eventTarget.callFunction(removeListener, [ | |
| SDK.RemoteObject.toCallArgument(this._type), SDK.RemoteObject.toCallArgument(this._originalHandler), | |
| SDK.RemoteObject.toCallArgument(this._useCapture) | |
| ])); | |
| } | |
| return this._customRemoveFunction | |
| .callFunction( | |
| callCustomRemove, | |
| [ | |
| SDK.RemoteObject.toCallArgument(this._type), | |
| SDK.RemoteObject.toCallArgument(this._originalHandler), | |
| SDK.RemoteObject.toCallArgument(this._useCapture), | |
| SDK.RemoteObject.toCallArgument(this._passive), | |
| ]) | |
| .then(() => undefined); | |
| /** | |
| * @param {string} type | |
| * @param {function()} listener | |
| * @param {boolean} useCapture | |
| * @param {boolean} passive | |
| * @this {Function} | |
| * @suppressReceiverCheck | |
| */ | |
| function callCustomRemove(type, listener, useCapture, passive) { | |
| this.call(null, type, listener, useCapture, passive); | |
| } | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| canTogglePassive() { | |
| return this._origin !== EventListener.Origin.FrameworkUser; | |
| } | |
| /** | |
| * @return {!Promise<undefined>} | |
| */ | |
| togglePassive() { | |
| return /** @type {!Promise<undefined>} */ (this._eventTarget.callFunction(callTogglePassive, [ | |
| SDK.RemoteObject.toCallArgument(this._type), | |
| SDK.RemoteObject.toCallArgument(this._originalHandler), | |
| SDK.RemoteObject.toCallArgument(this._useCapture), | |
| SDK.RemoteObject.toCallArgument(this._passive), | |
| ])); | |
| /** | |
| * @param {string} type | |
| * @param {function()} listener | |
| * @param {boolean} useCapture | |
| * @param {boolean} passive | |
| * @this {Object} | |
| * @suppressReceiverCheck | |
| */ | |
| function callTogglePassive(type, listener, useCapture, passive) { | |
| this.removeEventListener(type, listener, {capture: useCapture}); | |
| this.addEventListener(type, listener, {capture: useCapture, passive: !passive}); | |
| } | |
| } | |
| /** | |
| * @return {!EventListener.Origin} | |
| */ | |
| origin() { | |
| return this._origin; | |
| } | |
| markAsFramework() { | |
| this._origin = EventListener.Origin.Framework; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| isScrollBlockingType() { | |
| return this._type === 'touchstart' || this._type === 'touchmove' || this._type === 'mousewheel' || | |
| this._type === 'wheel'; | |
| } | |
| } | |
| /** @enum {string} */ | |
| EventListener.Origin = { | |
| Raw: 'Raw', | |
| Framework: 'Framework', | |
| FrameworkUser: 'FrameworkUser' | |
| }; | |
| export class EventListenerBreakpoint { | |
| /** | |
| * @param {string} instrumentationName | |
| * @param {string} eventName | |
| * @param {!Array<string>} eventTargetNames | |
| * @param {string} category | |
| * @param {string} title | |
| */ | |
| constructor(instrumentationName, eventName, eventTargetNames, category, title) { | |
| this._instrumentationName = instrumentationName; | |
| this._eventName = eventName; | |
| this._eventTargetNames = eventTargetNames; | |
| this._category = category; | |
| this._title = title; | |
| this._enabled = false; | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| category() { | |
| return this._category; | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| enabled() { | |
| return this._enabled; | |
| } | |
| /** | |
| * @param {boolean} enabled | |
| */ | |
| setEnabled(enabled) { | |
| if (this._enabled === enabled) { | |
| return; | |
| } | |
| this._enabled = enabled; | |
| for (const model of SDK.targetManager.models(DOMDebuggerModel)) { | |
| this._updateOnModel(model); | |
| } | |
| } | |
| /** | |
| * @param {!DOMDebuggerModel} model | |
| */ | |
| _updateOnModel(model) { | |
| if (this._instrumentationName) { | |
| if (this._enabled) { | |
| model._agent.setInstrumentationBreakpoint(this._instrumentationName); | |
| } else { | |
| model._agent.removeInstrumentationBreakpoint(this._instrumentationName); | |
| } | |
| } else { | |
| for (const eventTargetName of this._eventTargetNames) { | |
| if (this._enabled) { | |
| model._agent.setEventListenerBreakpoint(this._eventName, eventTargetName); | |
| } else { | |
| model._agent.removeEventListenerBreakpoint(this._eventName, eventTargetName); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * @return {string} | |
| */ | |
| title() { | |
| return this._title; | |
| } | |
| } | |
| EventListenerBreakpoint._listener = 'listener:'; | |
| EventListenerBreakpoint._instrumentation = 'instrumentation:'; | |
| /** | |
| * @implements {SDK.SDKModelObserver<!DOMDebuggerModel>} | |
| */ | |
| export class DOMDebuggerManager { | |
| constructor() { | |
| this._xhrBreakpointsSetting = Common.settings.createLocalSetting('xhrBreakpoints', []); | |
| /** @type {!Map<string, boolean>} */ | |
| this._xhrBreakpoints = new Map(); | |
| for (const breakpoint of this._xhrBreakpointsSetting.get()) { | |
| this._xhrBreakpoints.set(breakpoint.url, breakpoint.enabled); | |
| } | |
| /** @type {!Array<!EventListenerBreakpoint>} */ | |
| this._eventListenerBreakpoints = []; | |
| this._createInstrumentationBreakpoints( | |
| Common.UIString('Animation'), | |
| ['requestAnimationFrame', 'cancelAnimationFrame', 'requestAnimationFrame.callback']); | |
| this._createInstrumentationBreakpoints( | |
| Common.UIString('Canvas'), ['canvasContextCreated', 'webglErrorFired', 'webglWarningFired']); | |
| this._createInstrumentationBreakpoints( | |
| Common.UIString('Geolocation'), ['Geolocation.getCurrentPosition', 'Geolocation.watchPosition']); | |
| this._createInstrumentationBreakpoints(Common.UIString('Notification'), ['Notification.requestPermission']); | |
| this._createInstrumentationBreakpoints(Common.UIString('Parse'), ['Element.setInnerHTML', 'Document.write']); | |
| this._createInstrumentationBreakpoints(Common.UIString('Script'), ['scriptFirstStatement', 'scriptBlockedByCSP']); | |
| this._createInstrumentationBreakpoints( | |
| Common.UIString('Timer'), | |
| ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setTimeout.callback', 'setInterval.callback']); | |
| this._createInstrumentationBreakpoints(Common.UIString('Window'), ['DOMWindow.close']); | |
| this._createInstrumentationBreakpoints( | |
| Common.UIString('WebAudio'), | |
| ['audioContextCreated', 'audioContextClosed', 'audioContextResumed', 'audioContextSuspended']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('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( | |
| Common.UIString('Picture-in-Picture'), ['enterpictureinpicture', 'leavepictureinpicture'], ['video']); | |
| this._createEventListenerBreakpoints(Common.UIString('Picture-in-Picture'), ['resize'], ['PictureInPictureWindow']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('Clipboard'), ['copy', 'cut', 'paste', 'beforecopy', 'beforecut', 'beforepaste'], ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('Control'), | |
| ['resize', 'scroll', 'zoom', 'focus', 'blur', 'select', 'change', 'submit', 'reset'], ['*']); | |
| this._createEventListenerBreakpoints(Common.UIString('Device'), ['deviceorientation', 'devicemotion'], ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('DOM Mutation'), | |
| [ | |
| 'DOMActivate', 'DOMFocusIn', 'DOMFocusOut', 'DOMAttrModified', 'DOMCharacterDataModified', 'DOMNodeInserted', | |
| 'DOMNodeInsertedIntoDocument', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument', 'DOMSubtreeModified', | |
| 'DOMContentLoaded' | |
| ], | |
| ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('Drag / drop'), ['drag', 'dragstart', 'dragend', 'dragenter', 'dragover', 'dragleave', 'drop'], | |
| ['*']); | |
| this._createEventListenerBreakpoints(Common.UIString('Keyboard'), ['keydown', 'keyup', 'keypress', 'input'], ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('Load'), ['load', 'beforeunload', 'unload', 'abort', 'error', 'hashchange', 'popstate'], ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('Mouse'), | |
| [ | |
| 'auxclick', 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout', 'mouseenter', | |
| 'mouseleave', 'mousewheel', 'wheel', 'contextmenu' | |
| ], | |
| ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('Pointer'), | |
| [ | |
| 'pointerover', 'pointerout', 'pointerenter', 'pointerleave', 'pointerdown', 'pointerup', 'pointermove', | |
| 'pointercancel', 'gotpointercapture', 'lostpointercapture', 'pointerrawupdate' | |
| ], | |
| ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('Touch'), ['touchstart', 'touchmove', 'touchend', 'touchcancel'], ['*']); | |
| this._createEventListenerBreakpoints(Common.UIString('Worker'), ['message', 'messageerror'], ['*']); | |
| this._createEventListenerBreakpoints( | |
| Common.UIString('XHR'), | |
| ['readystatechange', 'load', 'loadstart', 'loadend', 'abort', 'error', 'progress', 'timeout'], | |
| ['xmlhttprequest', 'xmlhttprequestupload']); | |
| this._resolveEventListenerBreakpoint('instrumentation:setTimeout.callback')._title = | |
| Common.UIString('setTimeout fired'); | |
| this._resolveEventListenerBreakpoint('instrumentation:setInterval.callback')._title = | |
| Common.UIString('setInterval fired'); | |
| this._resolveEventListenerBreakpoint('instrumentation:scriptFirstStatement')._title = | |
| Common.UIString('Script First Statement'); | |
| this._resolveEventListenerBreakpoint('instrumentation:scriptBlockedByCSP')._title = | |
| Common.UIString('Script Blocked by Content Security Policy'); | |
| this._resolveEventListenerBreakpoint('instrumentation:requestAnimationFrame')._title = | |
| Common.UIString('Request Animation Frame'); | |
| this._resolveEventListenerBreakpoint('instrumentation:cancelAnimationFrame')._title = | |
| Common.UIString('Cancel Animation Frame'); | |
| this._resolveEventListenerBreakpoint('instrumentation:requestAnimationFrame.callback')._title = | |
| Common.UIString('Animation Frame Fired'); | |
| this._resolveEventListenerBreakpoint('instrumentation:webglErrorFired')._title = | |
| Common.UIString('WebGL Error Fired'); | |
| this._resolveEventListenerBreakpoint('instrumentation:webglWarningFired')._title = | |
| Common.UIString('WebGL Warning Fired'); | |
| this._resolveEventListenerBreakpoint('instrumentation:Element.setInnerHTML')._title = | |
| Common.UIString('Set innerHTML'); | |
| this._resolveEventListenerBreakpoint('instrumentation:canvasContextCreated')._title = | |
| Common.UIString('Create canvas context'); | |
| this._resolveEventListenerBreakpoint('instrumentation:Geolocation.getCurrentPosition')._title = | |
| 'getCurrentPosition'; | |
| this._resolveEventListenerBreakpoint('instrumentation:Geolocation.watchPosition')._title = 'watchPosition'; | |
| this._resolveEventListenerBreakpoint('instrumentation:Notification.requestPermission')._title = 'requestPermission'; | |
| this._resolveEventListenerBreakpoint('instrumentation:DOMWindow.close')._title = 'window.close'; | |
| this._resolveEventListenerBreakpoint('instrumentation:Document.write')._title = 'document.write'; | |
| this._resolveEventListenerBreakpoint('instrumentation:audioContextCreated')._title = | |
| Common.UIString('Create AudioContext'); | |
| this._resolveEventListenerBreakpoint('instrumentation:audioContextClosed')._title = | |
| Common.UIString('Close AudioContext'); | |
| this._resolveEventListenerBreakpoint('instrumentation:audioContextResumed')._title = | |
| Common.UIString('Resume AudioContext'); | |
| this._resolveEventListenerBreakpoint('instrumentation:audioContextSuspended')._title = | |
| Common.UIString('Suspend AudioContext'); | |
| SDK.targetManager.observeModels(SDK.DOMDebuggerModel, this); | |
| } | |
| /** | |
| * @param {string} category | |
| * @param {!Array<string>} instrumentationNames | |
| */ | |
| _createInstrumentationBreakpoints(category, instrumentationNames) { | |
| for (const instrumentationName of instrumentationNames) { | |
| this._eventListenerBreakpoints.push( | |
| new EventListenerBreakpoint(instrumentationName, '', [], category, instrumentationName)); | |
| } | |
| } | |
| /** | |
| * @param {string} category | |
| * @param {!Array<string>} eventNames | |
| * @param {!Array<string>} eventTargetNames | |
| */ | |
| _createEventListenerBreakpoints(category, eventNames, eventTargetNames) { | |
| for (const eventName of eventNames) { | |
| this._eventListenerBreakpoints.push( | |
| new EventListenerBreakpoint('', eventName, eventTargetNames, category, eventName)); | |
| } | |
| } | |
| /** | |
| * @param {string} eventName | |
| * @param {string=} eventTargetName | |
| * @return {?EventListenerBreakpoint} | |
| */ | |
| _resolveEventListenerBreakpoint(eventName, eventTargetName) { | |
| const instrumentationPrefix = 'instrumentation:'; | |
| const listenerPrefix = 'listener:'; | |
| let instrumentationName = ''; | |
| if (eventName.startsWith(instrumentationPrefix)) { | |
| instrumentationName = eventName.substring(instrumentationPrefix.length); | |
| eventName = ''; | |
| } else if (eventName.startsWith(listenerPrefix)) { | |
| eventName = eventName.substring(listenerPrefix.length); | |
| } else { | |
| return null; | |
| } | |
| eventTargetName = (eventTargetName || '*').toLowerCase(); | |
| let result = null; | |
| for (const breakpoint of this._eventListenerBreakpoints) { | |
| if (instrumentationName && breakpoint._instrumentationName === instrumentationName) { | |
| result = breakpoint; | |
| } | |
| if (eventName && breakpoint._eventName === eventName && | |
| breakpoint._eventTargetNames.indexOf(eventTargetName) !== -1) { | |
| result = breakpoint; | |
| } | |
| if (!result && eventName && breakpoint._eventName === eventName && | |
| breakpoint._eventTargetNames.indexOf('*') !== -1) { | |
| result = breakpoint; | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * @return {!Array<!EventListenerBreakpoint>} | |
| */ | |
| eventListenerBreakpoints() { | |
| return this._eventListenerBreakpoints.slice(); | |
| } | |
| /** | |
| * @param {!Object} auxData | |
| * @return {string} | |
| */ | |
| resolveEventListenerBreakpointTitle(auxData) { | |
| const id = auxData['eventName']; | |
| if (id === 'instrumentation:webglErrorFired' && auxData['webglErrorName']) { | |
| let errorName = auxData['webglErrorName']; | |
| // If there is a hex code of the error, display only this. | |
| errorName = errorName.replace(/^.*(0x[0-9a-f]+).*$/i, '$1'); | |
| return Common.UIString('WebGL Error Fired (%s)', errorName); | |
| } | |
| if (id === 'instrumentation:scriptBlockedByCSP' && auxData['directiveText']) { | |
| return Common.UIString('Script blocked due to Content Security Policy directive: %s', auxData['directiveText']); | |
| } | |
| const breakpoint = this._resolveEventListenerBreakpoint(id, auxData['targetName']); | |
| if (!breakpoint) { | |
| return ''; | |
| } | |
| if (auxData['targetName']) { | |
| return auxData['targetName'] + '.' + breakpoint._title; | |
| } | |
| return breakpoint._title; | |
| } | |
| /** | |
| * @param {!Object} auxData | |
| * @return {?EventListenerBreakpoint} | |
| */ | |
| resolveEventListenerBreakpoint(auxData) { | |
| return this._resolveEventListenerBreakpoint(auxData['eventName'], auxData['targetName']); | |
| } | |
| /** | |
| * @return {!Map<string, boolean>} | |
| */ | |
| xhrBreakpoints() { | |
| return this._xhrBreakpoints; | |
| } | |
| _saveXHRBreakpoints() { | |
| const breakpoints = []; | |
| for (const url of this._xhrBreakpoints.keys()) { | |
| breakpoints.push({url: url, enabled: this._xhrBreakpoints.get(url)}); | |
| } | |
| this._xhrBreakpointsSetting.set(breakpoints); | |
| } | |
| /** | |
| * @param {string} url | |
| * @param {boolean} enabled | |
| */ | |
| addXHRBreakpoint(url, enabled) { | |
| this._xhrBreakpoints.set(url, enabled); | |
| if (enabled) { | |
| for (const model of SDK.targetManager.models(DOMDebuggerModel)) { | |
| model._agent.setXHRBreakpoint(url); | |
| } | |
| } | |
| this._saveXHRBreakpoints(); | |
| } | |
| /** | |
| * @param {string} url | |
| */ | |
| removeXHRBreakpoint(url) { | |
| const enabled = this._xhrBreakpoints.get(url); | |
| this._xhrBreakpoints.delete(url); | |
| if (enabled) { | |
| for (const model of SDK.targetManager.models(DOMDebuggerModel)) { | |
| model._agent.removeXHRBreakpoint(url); | |
| } | |
| } | |
| this._saveXHRBreakpoints(); | |
| } | |
| /** | |
| * @param {string} url | |
| * @param {boolean} enabled | |
| */ | |
| toggleXHRBreakpoint(url, enabled) { | |
| this._xhrBreakpoints.set(url, enabled); | |
| for (const model of SDK.targetManager.models(DOMDebuggerModel)) { | |
| if (enabled) { | |
| model._agent.setXHRBreakpoint(url); | |
| } else { | |
| model._agent.removeXHRBreakpoint(url); | |
| } | |
| } | |
| this._saveXHRBreakpoints(); | |
| } | |
| /** | |
| * @override | |
| * @param {!DOMDebuggerModel} domDebuggerModel | |
| */ | |
| modelAdded(domDebuggerModel) { | |
| for (const url of this._xhrBreakpoints.keys()) { | |
| if (this._xhrBreakpoints.get(url)) { | |
| domDebuggerModel._agent.setXHRBreakpoint(url); | |
| } | |
| } | |
| for (const breakpoint of this._eventListenerBreakpoints) { | |
| if (breakpoint._enabled) { | |
| breakpoint._updateOnModel(domDebuggerModel); | |
| } | |
| } | |
| } | |
| /** | |
| * @override | |
| * @param {!DOMDebuggerModel} domDebuggerModel | |
| */ | |
| modelRemoved(domDebuggerModel) { | |
| } | |
| } | |
| /* Legacy exported object */ | |
| self.SDK = self.SDK || {}; | |
| /* Legacy exported object */ | |
| SDK = SDK || {}; | |
| /** @constructor */ | |
| SDK.DOMDebuggerModel = DOMDebuggerModel; | |
| /** @enum {symbol} */ | |
| SDK.DOMDebuggerModel.Events = Events; | |
| /** @constructor */ | |
| SDK.DOMDebuggerModel.DOMBreakpoint = DOMBreakpoint; | |
| /** @constructor */ | |
| SDK.DOMDebuggerModel.EventListenerBreakpoint = EventListenerBreakpoint; | |
| /** @constructor */ | |
| SDK.EventListener = EventListener; | |
| /** @constructor */ | |
| SDK.DOMDebuggerManager = DOMDebuggerManager; | |
| SDK.SDKModel.register(SDK.DOMDebuggerModel, SDK.Target.Capability.DOM, false); | |
| /** @typedef {Protocol.DOMDebugger.DOMBreakpointType} */ | |
| SDK.DOMDebuggerModel.DOMBreakpoint.Type = Protocol.DOMDebugger.DOMBreakpointType; | |
| /** @type {!SDK.DOMDebuggerManager} */ | |
| SDK.domDebuggerManager; |