| /* |
| * 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; |