UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

987 lines (883 loc) 33.5 kB
/* * Copyright (C) 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @unrestricted */ Bindings.BreakpointManager = class extends Common.Object { /** * @param {!Workspace.Workspace} workspace * @param {!SDK.TargetManager} targetManager * @param {!Bindings.DebuggerWorkspaceBinding} debuggerWorkspaceBinding */ constructor(workspace, targetManager, debuggerWorkspaceBinding) { super(); this._storage = new Bindings.BreakpointManager.Storage(); this._workspace = workspace; this._targetManager = targetManager; this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; /** @type {!Map<!Workspace.UISourceCode, !Map<number, !Map<number, !Array<!Bindings.BreakpointManager.Breakpoint>>>>} */ this._breakpointsForUISourceCode = new Map(); /** @type {!Map<string, !Bindings.BreakpointManager.Breakpoint>} */ this._breakpointByStorageId = new Map(); this._workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, this._projectRemoved, this); this._workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this); this._workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); } /** * @param {string} url * @param {number} lineNumber * @param {number} columnNumber * @return {string} */ static _breakpointStorageId(url, lineNumber, columnNumber) { if (!url) return ''; return url + ':' + lineNumber + ':' + columnNumber; } /** * @param {string} fromURL * @param {!Workspace.UISourceCode} toSourceCode */ copyBreakpoints(fromURL, toSourceCode) { const breakpointItems = this._storage.breakpointItems(fromURL); for (const item of breakpointItems) this.setBreakpoint(toSourceCode, item.lineNumber, item.columnNumber, item.condition, item.enabled); } /** * @param {!Workspace.UISourceCode} uiSourceCode */ _restoreBreakpoints(uiSourceCode) { const url = uiSourceCode.url(); if (!url) return; this._storage.mute(); const breakpointItems = this._storage.breakpointItems(url); for (const item of breakpointItems) this._innerSetBreakpoint(uiSourceCode, item.lineNumber, item.columnNumber, item.condition, item.enabled); this._storage.unmute(); } /** * @param {!Common.Event} event */ _uiSourceCodeAdded(event) { const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); this._restoreBreakpoints(uiSourceCode); } /** * @param {!Common.Event} event */ _uiSourceCodeRemoved(event) { const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); this._removeUISourceCode(uiSourceCode); } /** * @param {!Workspace.UISourceCode} uiSourceCode */ _removeUISourceCode(uiSourceCode) { const breakpoints = uiSourceCode[Bindings.BreakpointManager._breakpointsSymbol] || new Set(); for (const breakpoint of breakpoints) breakpoint._resetLocations(); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {number} lineNumber * @param {number} columnNumber * @param {string} condition * @param {boolean} enabled * @return {!Bindings.BreakpointManager.Breakpoint} */ setBreakpoint(uiSourceCode, lineNumber, columnNumber, condition, enabled) { let uiLocation = new Workspace.UILocation(uiSourceCode, lineNumber, columnNumber); const normalizedLocation = this._debuggerWorkspaceBinding.normalizeUILocation(uiLocation); if (normalizedLocation.id() !== uiLocation.id()) { Common.Revealer.reveal(normalizedLocation); uiLocation = normalizedLocation; } return this._innerSetBreakpoint( uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber, condition, enabled); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {number} lineNumber * @param {number} columnNumber * @param {string} condition * @param {boolean} enabled * @return {!Bindings.BreakpointManager.Breakpoint} */ _innerSetBreakpoint(uiSourceCode, lineNumber, columnNumber, condition, enabled) { const itemId = Bindings.BreakpointManager._breakpointStorageId(uiSourceCode.url(), lineNumber, columnNumber); let breakpoint = this._breakpointByStorageId.get(itemId); if (breakpoint) { breakpoint.setPrimaryUISourceCode(uiSourceCode); breakpoint._updateState(condition, enabled); breakpoint._updateBreakpoint(); return breakpoint; } breakpoint = new Bindings.BreakpointManager.Breakpoint( this, uiSourceCode, uiSourceCode.url(), lineNumber, columnNumber, condition, enabled); this._breakpointByStorageId.set(itemId, breakpoint); return breakpoint; } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {number} lineNumber * @return {!Array<!Bindings.BreakpointManager.Breakpoint>} */ findBreakpoints(uiSourceCode, lineNumber) { const breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); const lineBreakpoints = breakpoints ? breakpoints.get(lineNumber) : null; return lineBreakpoints ? lineBreakpoints.valuesArray()[0] : []; } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {number} lineNumber * @param {number} columnNumber * @return {?Bindings.BreakpointManager.Breakpoint} */ findBreakpoint(uiSourceCode, lineNumber, columnNumber) { const breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); const lineBreakpoints = breakpoints ? breakpoints.get(lineNumber) : null; const columnBreakpoints = lineBreakpoints ? lineBreakpoints.get(columnNumber) : null; return columnBreakpoints ? columnBreakpoints[0] : null; } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {!TextUtils.TextRange} textRange * @return {!Promise<!Array<!Workspace.UILocation>>} */ possibleBreakpoints(uiSourceCode, textRange) { const startLocation = Bindings.debuggerWorkspaceBinding.uiLocationToRawLocation( uiSourceCode, textRange.startLine, textRange.startColumn); const endLocation = Bindings.debuggerWorkspaceBinding.uiLocationToRawLocation(uiSourceCode, textRange.endLine, textRange.endColumn); if (!startLocation || !endLocation || startLocation.debuggerModel !== endLocation.debuggerModel) return Promise.resolve([]); return startLocation.debuggerModel .getPossibleBreakpoints(startLocation, endLocation, /* restrictToFunction */ false) .then(toUILocations.bind(this)); /** * @this {!Bindings.BreakpointManager} * @param {!Array<!SDK.DebuggerModel.BreakLocation>} locations * @return {!Array<!Workspace.UILocation>} */ function toUILocations(locations) { let sortedLocations = locations.map(location => this._debuggerWorkspaceBinding.rawLocationToUILocation(location)); sortedLocations = sortedLocations.filter(location => location && location.uiSourceCode === uiSourceCode); sortedLocations.sort(Workspace.UILocation.comparator); if (!sortedLocations.length) return []; const result = [sortedLocations[0]]; let lastLocation = sortedLocations[0]; for (let i = 1; i < sortedLocations.length; ++i) { if (sortedLocations[i].id() === lastLocation.id()) continue; result.push(sortedLocations[i]); lastLocation = sortedLocations[i]; } return result; } } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {!Array.<!Bindings.BreakpointManager.Breakpoint>} */ breakpointsForUISourceCode(uiSourceCode) { let result = []; const uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); const breakpoints = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.valuesArray() : []; for (let i = 0; i < breakpoints.length; ++i) { const lineBreakpoints = breakpoints[i]; const columnBreakpointArrays = lineBreakpoints ? lineBreakpoints.valuesArray() : []; result = result.concat.apply(result, columnBreakpointArrays); } return result; } /** * @return {!Array.<!Bindings.BreakpointManager.Breakpoint>} */ _allBreakpoints() { let result = []; const uiSourceCodes = this._breakpointsForUISourceCode.keysArray(); for (let i = 0; i < uiSourceCodes.length; ++i) result = result.concat(this.breakpointsForUISourceCode(uiSourceCodes[i])); return result; } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {!Array.<!{breakpoint: !Bindings.BreakpointManager.Breakpoint, uiLocation: !Workspace.UILocation}>} */ breakpointLocationsForUISourceCode(uiSourceCode) { const uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); const lineNumbers = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.keysArray() : []; const result = []; for (let i = 0; i < lineNumbers.length; ++i) { const lineBreakpoints = uiSourceCodeBreakpoints.get(lineNumbers[i]); const columnNumbers = lineBreakpoints.keysArray(); for (let j = 0; j < columnNumbers.length; ++j) { const columnBreakpoints = lineBreakpoints.get(columnNumbers[j]); const lineNumber = parseInt(lineNumbers[i], 10); const columnNumber = parseInt(columnNumbers[j], 10); for (let k = 0; k < columnBreakpoints.length; ++k) { const breakpoint = columnBreakpoints[k]; const uiLocation = uiSourceCode.uiLocation(lineNumber, columnNumber); result.push({breakpoint: breakpoint, uiLocation: uiLocation}); } } } return result; } /** * @return {!Array.<!{breakpoint: !Bindings.BreakpointManager.Breakpoint, uiLocation: !Workspace.UILocation}>} */ allBreakpointLocations() { let result = []; const uiSourceCodes = this._breakpointsForUISourceCode.keysArray(); for (let i = 0; i < uiSourceCodes.length; ++i) result = result.concat(this.breakpointLocationsForUISourceCode(uiSourceCodes[i])); return result; } /** * @param {boolean} toggleState */ toggleAllBreakpoints(toggleState) { const breakpoints = this._allBreakpoints(); for (let i = 0; i < breakpoints.length; ++i) breakpoints[i].setEnabled(toggleState); } removeAllBreakpoints() { const breakpoints = this._allBreakpoints(); for (let i = 0; i < breakpoints.length; ++i) breakpoints[i].remove(false /* keepInStorage */); } /** * @param {!Set<!Bindings.BreakpointManager.Breakpoint>} selectedBreakpoints */ removeOtherBreakpoints(selectedBreakpoints) { const allBreakpoints = this._allBreakpoints(); allBreakpoints.forEach(breakpoint => { if (!selectedBreakpoints.has(breakpoint)) breakpoint.remove(false /* keepInStorage */); }); } _projectRemoved(event) { const project = /** @type {!Workspace.Project} */ (event.data); const uiSourceCodes = project.uiSourceCodes(); for (let i = 0; i < uiSourceCodes.length; ++i) this._removeUISourceCode(uiSourceCodes[i]); } /** * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint * @param {boolean} removeFromStorage */ _removeBreakpoint(breakpoint, removeFromStorage) { if (removeFromStorage) this._storage._removeBreakpoint(breakpoint); this._breakpointByStorageId.delete(breakpoint._breakpointStorageId()); } /** * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint * @param {!Workspace.UILocation} uiLocation */ _uiLocationAdded(breakpoint, uiLocation) { let breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); if (!breakpoints) { breakpoints = new Map(); this._breakpointsForUISourceCode.set(uiLocation.uiSourceCode, breakpoints); } let lineBreakpoints = breakpoints.get(uiLocation.lineNumber); if (!lineBreakpoints) { lineBreakpoints = new Map(); breakpoints.set(uiLocation.lineNumber, lineBreakpoints); } let columnBreakpoints = lineBreakpoints.get(uiLocation.columnNumber); if (!columnBreakpoints) { columnBreakpoints = []; lineBreakpoints.set(uiLocation.columnNumber, columnBreakpoints); } columnBreakpoints.push(breakpoint); this.dispatchEventToListeners( Bindings.BreakpointManager.Events.BreakpointAdded, {breakpoint: breakpoint, uiLocation: uiLocation}); } /** * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint * @param {!Workspace.UILocation} uiLocation */ _uiLocationRemoved(breakpoint, uiLocation) { const breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); if (!breakpoints) return; const lineBreakpoints = breakpoints.get(uiLocation.lineNumber); if (!lineBreakpoints) return; const columnBreakpoints = lineBreakpoints.get(uiLocation.columnNumber); if (!columnBreakpoints) return; columnBreakpoints.remove(breakpoint); if (!columnBreakpoints.length) lineBreakpoints.remove(uiLocation.columnNumber); if (!lineBreakpoints.size) breakpoints.remove(uiLocation.lineNumber); if (!breakpoints.size) this._breakpointsForUISourceCode.remove(uiLocation.uiSourceCode); this.dispatchEventToListeners( Bindings.BreakpointManager.Events.BreakpointRemoved, {breakpoint: breakpoint, uiLocation: uiLocation}); } }; /** @enum {symbol} */ Bindings.BreakpointManager.Events = { BreakpointAdded: Symbol('breakpoint-added'), BreakpointRemoved: Symbol('breakpoint-removed') }; /** * @unrestricted * @implements {SDK.SDKModelObserver<!SDK.DebuggerModel>} */ Bindings.BreakpointManager.Breakpoint = class { /** * @param {!Bindings.BreakpointManager} breakpointManager * @param {!Workspace.UISourceCode} primaryUISourceCode * @param {string} url * @param {number} lineNumber * @param {number} columnNumber * @param {string} condition * @param {boolean} enabled */ constructor(breakpointManager, primaryUISourceCode, url, lineNumber, columnNumber, condition, enabled) { this._breakpointManager = breakpointManager; this._url = url; this._lineNumber = lineNumber; this._columnNumber = columnNumber; this.setPrimaryUISourceCode(primaryUISourceCode); /** @type {!Map<string, number>} */ this._numberOfDebuggerLocationForUILocation = new Map(); /** @type {string} */ this._condition; /** @type {boolean} */ this._enabled; /** @type {boolean} */ this._isRemoved; /** @type {!Workspace.UILocation|undefined} */ this._fakePrimaryLocation; this._currentState = null; /** @type {!Map.<!SDK.DebuggerModel, !Bindings.BreakpointManager.ModelBreakpoint>}*/ this._modelBreakpoints = new Map(); this._updateState(condition, enabled); this._breakpointManager._targetManager.observeModels(SDK.DebuggerModel, this); } refreshInDebugger() { if (this._isRemoved) return; for (const breakpoint of this._modelBreakpoints.values()) breakpoint._refreshBreakpoint(); } /** * @override * @param {!SDK.DebuggerModel} debuggerModel */ modelAdded(debuggerModel) { const debuggerWorkspaceBinding = this._breakpointManager._debuggerWorkspaceBinding; this._modelBreakpoints.set( debuggerModel, new Bindings.BreakpointManager.ModelBreakpoint(debuggerModel, this, debuggerWorkspaceBinding)); } /** * @override * @param {!SDK.DebuggerModel} debuggerModel */ modelRemoved(debuggerModel) { const modelBreakpoint = this._modelBreakpoints.remove(debuggerModel); modelBreakpoint._cleanUpAfterDebuggerIsGone(); modelBreakpoint._removeEventListeners(); } /** * @param {?Workspace.UISourceCode} primaryUISourceCode */ setPrimaryUISourceCode(primaryUISourceCode) { const symbol = Bindings.BreakpointManager._breakpointsSymbol; if (this._primaryUISourceCode) this._primaryUISourceCode[symbol].delete(this); this._primaryUISourceCode = primaryUISourceCode; if (!primaryUISourceCode) return; if (!this._primaryUISourceCode[symbol]) this._primaryUISourceCode[symbol] = new Set(); this._primaryUISourceCode[symbol].add(this); } /** * @return {string} */ url() { return this._url; } /** * @return {number} */ lineNumber() { return this._lineNumber; } /** * @return {number} */ columnNumber() { return this._columnNumber; } /** * @param {?Workspace.UILocation} oldUILocation * @param {!Workspace.UILocation} newUILocation */ _replaceUILocation(oldUILocation, newUILocation) { if (this._isRemoved) return; this._removeUILocation(oldUILocation, true); this._removeFakeBreakpointAtPrimaryLocation(); const current = (this._numberOfDebuggerLocationForUILocation.get(newUILocation.id()) || 0) + 1; this._numberOfDebuggerLocationForUILocation.set(newUILocation.id(), current); if (current === 1) this._breakpointManager._uiLocationAdded(this, newUILocation); } /** * @param {?Workspace.UILocation} uiLocation * @param {boolean=} muteCreationFakeBreakpoint */ _removeUILocation(uiLocation, muteCreationFakeBreakpoint) { if (!uiLocation || !this._numberOfDebuggerLocationForUILocation.has(uiLocation.id())) return; const current = (this._numberOfDebuggerLocationForUILocation.get(uiLocation.id()) || 0) - 1; this._numberOfDebuggerLocationForUILocation.set(uiLocation.id(), current); if (current !== 0) return; this._numberOfDebuggerLocationForUILocation.delete(uiLocation.id()); this._breakpointManager._uiLocationRemoved(this, uiLocation); if (!muteCreationFakeBreakpoint) this._fakeBreakpointAtPrimaryLocation(); } /** * @return {boolean} */ enabled() { return this._enabled; } /** * @param {boolean} enabled */ setEnabled(enabled) { this._updateState(this._condition, enabled); } /** * @return {string} */ condition() { return this._condition; } /** * @param {string} condition */ setCondition(condition) { this._updateState(condition, this._enabled); } /** * @param {string} condition * @param {boolean} enabled */ _updateState(condition, enabled) { if (this._enabled === enabled && this._condition === condition) return; this._enabled = enabled; this._condition = condition; this._breakpointManager._storage._updateBreakpoint(this); this._updateBreakpoint(); } _updateBreakpoint() { this._removeFakeBreakpointAtPrimaryLocation(); this._fakeBreakpointAtPrimaryLocation(); const modelBreakpoints = this._modelBreakpoints.valuesArray(); for (let i = 0; i < modelBreakpoints.length; ++i) modelBreakpoints[i]._scheduleUpdateInDebugger(); } /** * @param {boolean} keepInStorage */ remove(keepInStorage) { this._isRemoved = true; const removeFromStorage = !keepInStorage; this._removeFakeBreakpointAtPrimaryLocation(); const modelBreakpoints = this._modelBreakpoints.valuesArray(); for (let i = 0; i < modelBreakpoints.length; ++i) { modelBreakpoints[i]._scheduleUpdateInDebugger(); modelBreakpoints[i]._removeEventListeners(); } this.setPrimaryUISourceCode(null); this._breakpointManager._removeBreakpoint(this, removeFromStorage); this._breakpointManager._targetManager.unobserveModels(SDK.DebuggerModel, this); } /** * @param {!SDK.DebuggerModel} debuggerModel */ _updateInDebuggerForModel(debuggerModel) { this._modelBreakpoints.get(debuggerModel)._scheduleUpdateInDebugger(); } /** * @return {string} */ _breakpointStorageId() { return Bindings.BreakpointManager._breakpointStorageId(this._url, this._lineNumber, this._columnNumber); } _fakeBreakpointAtPrimaryLocation() { if (this._isRemoved || this._numberOfDebuggerLocationForUILocation.size || this._fakePrimaryLocation) return; if (!this._primaryUISourceCode) return; this._fakePrimaryLocation = this._primaryUISourceCode.uiLocation(this._lineNumber, this._columnNumber); if (this._fakePrimaryLocation) this._breakpointManager._uiLocationAdded(this, this._fakePrimaryLocation); } _removeFakeBreakpointAtPrimaryLocation() { if (this._fakePrimaryLocation) { this._breakpointManager._uiLocationRemoved(this, this._fakePrimaryLocation); delete this._fakePrimaryLocation; } } _resetLocations() { this.setPrimaryUISourceCode(null); this._removeFakeBreakpointAtPrimaryLocation(); const modelBreakpoints = this._modelBreakpoints.valuesArray(); for (let i = 0; i < modelBreakpoints.length; ++i) modelBreakpoints[i]._resetLocations(); } }; Bindings.BreakpointManager._breakpointsSymbol = Symbol('breakpoints'); /** * @unrestricted */ Bindings.BreakpointManager.ModelBreakpoint = class { /** * @param {!SDK.DebuggerModel} debuggerModel * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint * @param {!Bindings.DebuggerWorkspaceBinding} debuggerWorkspaceBinding */ constructor(debuggerModel, breakpoint, debuggerWorkspaceBinding) { this._debuggerModel = debuggerModel; this._breakpoint = breakpoint; this._debuggerWorkspaceBinding = debuggerWorkspaceBinding; this._liveLocations = new Bindings.LiveLocationPool(); /** @type {!Map<string, !Workspace.UILocation>} */ this._uiLocations = new Map(); this._debuggerModel.addEventListener( SDK.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this); this._debuggerModel.addEventListener( SDK.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this); this._hasPendingUpdate = false; this._isUpdating = false; this._cancelCallback = false; this._currentState = null; if (this._debuggerModel.debuggerEnabled()) this._scheduleUpdateInDebugger(); } _resetLocations() { for (const uiLocation of this._uiLocations.values()) this._breakpoint._removeUILocation(uiLocation); this._uiLocations.clear(); this._liveLocations.disposeAll(); } _scheduleUpdateInDebugger() { if (this._isUpdating) { this._hasPendingUpdate = true; return; } this._isUpdating = true; this._updateInDebugger(this._didUpdateInDebugger.bind(this)); } _didUpdateInDebugger() { this._isUpdating = false; if (this._hasPendingUpdate) { this._hasPendingUpdate = false; this._scheduleUpdateInDebugger(); } } /** * @return {boolean} */ _scriptDiverged() { const uiSourceCode = this._breakpoint._primaryUISourceCode; if (!uiSourceCode) return false; const scriptFile = this._debuggerWorkspaceBinding.scriptFile(uiSourceCode, this._debuggerModel); return !!scriptFile && scriptFile.hasDivergedFromVM(); } /** * @param {function()} callback * @return {!Promise} */ async _updateInDebugger(callback) { if (this._debuggerModel.target().isDisposed()) { this._cleanUpAfterDebuggerIsGone(); callback(); return; } const uiSourceCode = this._breakpoint._primaryUISourceCode; const lineNumber = this._breakpoint._lineNumber; const columnNumber = this._breakpoint._columnNumber; const condition = this._breakpoint.condition(); let debuggerLocation = uiSourceCode && Bindings.debuggerWorkspaceBinding.uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber); if (debuggerLocation && debuggerLocation.debuggerModel !== this._debuggerModel) debuggerLocation = null; let newState; if (this._breakpoint._isRemoved || !this._breakpoint.enabled() || this._scriptDiverged()) { newState = null; } else if (debuggerLocation) { const script = debuggerLocation.script(); if (script.sourceURL) { newState = new Bindings.BreakpointManager.Breakpoint.State( script.sourceURL, null, null, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition); } else { newState = new Bindings.BreakpointManager.Breakpoint.State( null, script.scriptId, script.hash, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition); } } else if (this._breakpoint._currentState && this._breakpoint._currentState.url) { const position = this._breakpoint._currentState; newState = new Bindings.BreakpointManager.Breakpoint.State( position.url, null, null, position.lineNumber, position.columnNumber, condition); } else if (uiSourceCode) { newState = new Bindings.BreakpointManager.Breakpoint.State( uiSourceCode.url(), null, null, lineNumber, columnNumber, condition); } if (this._debuggerId && Bindings.BreakpointManager.Breakpoint.State.equals(newState, this._currentState)) { callback(); return; } this._breakpoint._currentState = newState; if (this._debuggerId) { await this._refreshBreakpoint(); callback(); return; } if (!newState) { callback(); return; } let result; this._currentState = newState; if (newState.url) { result = await this._debuggerModel.setBreakpointByURL( newState.url, newState.lineNumber, newState.columnNumber, newState.condition); } else if (newState.scriptId && newState.scriptHash) { result = await this._debuggerModel.setBreakpointInAnonymousScript( newState.scriptId, newState.scriptHash, newState.lineNumber, newState.columnNumber, newState.condition); } if (result && result.breakpointId) this._didSetBreakpointInDebugger(callback, result.breakpointId, result.locations); else this._didSetBreakpointInDebugger(callback, null, []); } async _refreshBreakpoint() { if (!this._debuggerId) return; this._resetLocations(); await this._debuggerModel.removeBreakpoint(this._debuggerId); this._didRemoveFromDebugger(); this._currentState = null; this._scheduleUpdateInDebugger(); } /** * @param {function()} callback * @param {?Protocol.Debugger.BreakpointId} breakpointId * @param {!Array.<!SDK.DebuggerModel.Location>} locations */ _didSetBreakpointInDebugger(callback, breakpointId, locations) { if (this._cancelCallback) { this._cancelCallback = false; callback(); return; } if (!breakpointId) { this._breakpoint.remove(true); callback(); return; } this._debuggerId = breakpointId; this._debuggerModel.addBreakpointListener(this._debuggerId, this._breakpointResolved, this); for (let i = 0; i < locations.length; ++i) { if (!this._addResolvedLocation(locations[i])) break; } callback(); } _didRemoveFromDebugger() { if (this._cancelCallback) { this._cancelCallback = false; return; } this._resetLocations(); this._debuggerModel.removeBreakpointListener(this._debuggerId, this._breakpointResolved, this); delete this._debuggerId; } /** * @param {!Common.Event} event */ _breakpointResolved(event) { this._addResolvedLocation(/** @type {!SDK.DebuggerModel.Location}*/ (event.data)); } /** * @param {!SDK.DebuggerModel.Location} location * @param {!Bindings.LiveLocation} liveLocation */ _locationUpdated(location, liveLocation) { const uiLocation = liveLocation.uiLocation(); if (!uiLocation) return; const oldUILocation = this._uiLocations.get(location.id()) || null; this._uiLocations.set(location.id(), uiLocation); this._breakpoint._replaceUILocation(oldUILocation, uiLocation); } /** * @param {!SDK.DebuggerModel.Location} location * @return {boolean} */ _addResolvedLocation(location) { const uiLocation = this._debuggerWorkspaceBinding.rawLocationToUILocation(location); if (!uiLocation) return false; const breakpoint = this._breakpoint._breakpointManager.findBreakpoint( uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber); if (breakpoint && breakpoint !== this._breakpoint) { // location clash this._breakpoint.remove(false /* keepInStorage */); return false; } this._debuggerWorkspaceBinding.createLiveLocation( location, this._locationUpdated.bind(this, location), this._liveLocations); return true; } _cleanUpAfterDebuggerIsGone() { if (this._isUpdating) this._cancelCallback = true; this._resetLocations(); this._currentState = null; if (this._debuggerId) this._didRemoveFromDebugger(); } _removeEventListeners() { this._debuggerModel.removeEventListener( SDK.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this); this._debuggerModel.removeEventListener( SDK.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this); } }; Bindings.BreakpointManager.Breakpoint.State = class { /** * @param {?string} url * @param {?string} scriptId * @param {?string} scriptHash * @param {number} lineNumber * @param {number} columnNumber * @param {string} condition */ constructor(url, scriptId, scriptHash, lineNumber, columnNumber, condition) { this.url = url; this.scriptId = scriptId; this.scriptHash = scriptHash; this.lineNumber = lineNumber; this.columnNumber = columnNumber; this.condition = condition; } /** * @param {?Bindings.BreakpointManager.Breakpoint.State|undefined} stateA * @param {?Bindings.BreakpointManager.Breakpoint.State|undefined} stateB * @return {boolean} */ static equals(stateA, stateB) { if (!stateA || !stateB) return false; return stateA.url === stateB.url && stateA.scriptId === stateB.scriptId && stateA.scriptHash === stateB.scriptHash && stateA.lineNumber === stateB.lineNumber && stateA.columnNumber === stateB.columnNumber && stateA.condition === stateB.condition; } }; Bindings.BreakpointManager.Storage = class { constructor() { this._setting = Common.settings.createLocalSetting('breakpoints', []); /** @type {!Map<string, !Bindings.BreakpointManager.Storage.Item>} */ this._breakpoints = new Map(); const items = /** @type {!Array<!Bindings.BreakpointManager.Storage.Item>} */ (this._setting.get()); for (const item of items) { item.columnNumber = item.columnNumber || 0; this._breakpoints.set( Bindings.BreakpointManager._breakpointStorageId(item.url, item.lineNumber, item.columnNumber), item); } /** @type {boolean|undefined} */ this._muted; } mute() { this._muted = true; } unmute() { delete this._muted; } /** * @param {string} url * @return {!Array<!Bindings.BreakpointManager.Storage.Item>} */ breakpointItems(url) { return Array.from(this._breakpoints.values()).filter(item => item.url === url); } /** * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint */ _updateBreakpoint(breakpoint) { if (this._muted || !breakpoint._breakpointStorageId()) return; this._breakpoints.set(breakpoint._breakpointStorageId(), new Bindings.BreakpointManager.Storage.Item(breakpoint)); this._save(); } /** * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint */ _removeBreakpoint(breakpoint) { if (this._muted) return; this._breakpoints.delete(breakpoint._breakpointStorageId()); this._save(); } _save() { this._setting.set(Array.from(this._breakpoints.values())); } }; /** * @unrestricted */ Bindings.BreakpointManager.Storage.Item = class { /** * @param {!Bindings.BreakpointManager.Breakpoint} breakpoint */ constructor(breakpoint) { this.url = breakpoint._url; this.lineNumber = breakpoint.lineNumber(); this.columnNumber = breakpoint.columnNumber(); this.condition = breakpoint.condition(); this.enabled = breakpoint.enabled(); } }; /** @type {!Bindings.BreakpointManager} */ Bindings.breakpointManager;