/* * 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. */ /** * @constructor * @extends {WebInspector.Object} * @param {WebInspector.Setting} breakpointStorage * @param {WebInspector.DebuggerModel} debuggerModel * @param {WebInspector.Workspace} workspace */ WebInspector.BreakpointManager = function(breakpointStorage, debuggerModel, workspace) { this._storage = new WebInspector.BreakpointManager.Storage(this, breakpointStorage); this._debuggerModel = debuggerModel; this._workspace = workspace; this._breakpoints = new Map(); this._breakpointForDebuggerId = {}; this._breakpointsForUISourceCode = new Map(); this._sourceFilesWithRestoredBreakpoints = {}; this._debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointResolved, this._breakpointResolved, this); this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._projectWillReset, this); this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this); } WebInspector.BreakpointManager.Events = { BreakpointAdded: "breakpoint-added", BreakpointRemoved: "breakpoint-removed" } WebInspector.BreakpointManager.sourceFileId = function(uiSourceCode) { if (!uiSourceCode.url) return ""; var deobfuscatedPrefix = uiSourceCode.formatted() ? "deobfuscated:" : ""; return deobfuscatedPrefix + uiSourceCode.uri(); } WebInspector.BreakpointManager.prototype = { /** * @param {WebInspector.UISourceCode} uiSourceCode */ _restoreBreakpoints: function(uiSourceCode) { var sourceFileId = WebInspector.BreakpointManager.sourceFileId(uiSourceCode); if (!sourceFileId || this._sourceFilesWithRestoredBreakpoints[sourceFileId]) return; this._sourceFilesWithRestoredBreakpoints[sourceFileId] = true; // Erase provisional breakpoints prior to restoring them. for (var debuggerId in this._breakpointForDebuggerId) { var breakpoint = this._breakpointForDebuggerId[debuggerId]; if (breakpoint._sourceFileId !== sourceFileId) continue; breakpoint.remove(true); } this._storage._restoreBreakpoints(uiSourceCode); }, /** * @param {WebInspector.Event} event */ _uiSourceCodeAdded: function(event) { var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data); this._restoreBreakpoints(uiSourceCode); if (uiSourceCode.contentType() === WebInspector.resourceTypes.Script || uiSourceCode.contentType() === WebInspector.resourceTypes.Document) { uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this); uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._uiSourceCodeFormatted, this); } }, /** * @param {WebInspector.Event} event */ _uiSourceCodeFormatted: function(event) { var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.target); this._restoreBreakpoints(uiSourceCode); }, /** * @param {WebInspector.UISourceCode} uiSourceCode */ _resetBreakpoints: function(uiSourceCode) { var sourceFileId = WebInspector.BreakpointManager.sourceFileId(uiSourceCode); var breakpoints = this._breakpoints.keys(); for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; if (breakpoint._sourceFileId !== sourceFileId) return; if (breakpoint.enabled()) { breakpoint._removeFromDebugger(); breakpoint._setInDebugger(); } } }, /** * @param {WebInspector.Event} event */ _uiSourceCodeMappingChanged: function(event) { var identityHasChanged = /** @type {boolean} */ (event.data.identityHasChanged); if (!identityHasChanged) return; var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.target); this._resetBreakpoints(uiSourceCode); }, /** * @param {WebInspector.UISourceCode} uiSourceCode * @param {number} lineNumber * @param {string} condition * @param {boolean} enabled * @return {WebInspector.BreakpointManager.Breakpoint} */ setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled) { this._debuggerModel.setBreakpointsActive(true); return this._innerSetBreakpoint(uiSourceCode, lineNumber, condition, enabled); }, /** * @param {WebInspector.UISourceCode} uiSourceCode * @param {number} lineNumber * @param {string} condition * @param {boolean} enabled * @return {WebInspector.BreakpointManager.Breakpoint} */ _innerSetBreakpoint: function(uiSourceCode, lineNumber, condition, enabled) { var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber); if (breakpoint) { breakpoint._updateBreakpoint(condition, enabled); return breakpoint; } breakpoint = new WebInspector.BreakpointManager.Breakpoint(this, uiSourceCode, lineNumber, condition, enabled); this._breakpoints.put(breakpoint); return breakpoint; }, /** * @param {WebInspector.UISourceCode} uiSourceCode * @param {number} lineNumber * @return {?WebInspector.BreakpointManager.Breakpoint} */ findBreakpoint: function(uiSourceCode, lineNumber) { var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); var lineBreakpoints = breakpoints ? breakpoints[lineNumber] : null; return lineBreakpoints ? lineBreakpoints[0] : null; }, /** * @param {WebInspector.UISourceCode} uiSourceCode * @return {Array.} */ breakpointsForUISourceCode: function(uiSourceCode) { var result = []; var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; var uiLocation = breakpoint._primaryUILocation; if (uiLocation.uiSourceCode === uiSourceCode) result.push(breakpoint); } return result; }, /** * @return {Array.} */ allBreakpoints: function() { var result = []; var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); return breakpoints; }, /** * @param {WebInspector.UISourceCode} uiSourceCode * @return {Array.<{breakpoint: WebInspector.BreakpointManager.Breakpoint, uiLocation: WebInspector.UILocation}>} */ breakpointLocationsForUISourceCode: function(uiSourceCode) { var result = []; var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; var uiLocations = Object.values(breakpoint._uiLocations); for (var j = 0; j < uiLocations.length; ++j) { var uiLocation = uiLocations[j]; if (uiLocation.uiSourceCode === uiSourceCode) result.push({breakpoint: breakpoint, uiLocation: uiLocations[j]}); } } return result; }, /** * @return {Array.<{breakpoint: WebInspector.BreakpointManager.Breakpoint, uiLocation: WebInspector.UILocation}>} */ allBreakpointLocations: function() { var result = []; var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; var uiLocations = Object.values(breakpoint._uiLocations); for (var j = 0; j < uiLocations.length; ++j) result.push({breakpoint: breakpoint, uiLocation: uiLocations[j]}); } return result; }, /** * @param {boolean} toggleState */ toggleAllBreakpoints: function(toggleState) { var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; if (breakpoint.enabled() != toggleState) breakpoint.setEnabled(toggleState); } }, removeAllBreakpoints: function() { var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); for (var i = 0; i < breakpoints.length; ++i) breakpoints[i].remove(); }, reset: function() { // Remove all breakpoints from UI and debugger, do not update storage. this._storage._muted = true; this.removeAllBreakpoints(); delete this._storage._muted; // Remove all provisional breakpoints from the debugger. for (var debuggerId in this._breakpointForDebuggerId) this._debuggerModel.removeBreakpoint(debuggerId); this._breakpointForDebuggerId = {}; this._sourceFilesWithRestoredBreakpoints = {}; }, _projectWillReset: function(event) { var project = /** @type {WebInspector.Project} */ (event.data); var uiSourceCodes = project.uiSourceCodes(); for (var i = 0; i < uiSourceCodes.length; ++i) { var uiSourceCode = uiSourceCodes[i]; var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode) || []; for (var lineNumber in breakpoints) { var lineBreakpoints = breakpoints[lineNumber]; for (var j = 0; j < lineBreakpoints.length; ++j) { var breakpoint = lineBreakpoints[j]; breakpoint._resetLocations(); } } this._breakpointsForUISourceCode.remove(uiSourceCode); breakpoints = this.breakpointsForUISourceCode(uiSourceCode); for (var j = 0; j < breakpoints.length; ++j) { var breakpoint = breakpoints[j]; this._breakpoints.remove(breakpoint); } var sourceFileId = WebInspector.BreakpointManager.sourceFileId(uiSourceCode); delete this._sourceFilesWithRestoredBreakpoints[sourceFileId]; } }, _breakpointResolved: function(event) { var breakpointId = /** @type {DebuggerAgent.BreakpointId} */ (event.data.breakpointId); var location = /** @type {WebInspector.DebuggerModel.Location} */ (event.data.location); var breakpoint = this._breakpointForDebuggerId[breakpointId]; if (!breakpoint) return; if (!this._breakpoints.contains(breakpoint)) this._breakpoints.put(breakpoint); breakpoint._addResolvedLocation(location); }, /** * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint * @param {boolean} removeFromStorage */ _removeBreakpoint: function(breakpoint, removeFromStorage) { console.assert(!breakpoint._debuggerId) this._breakpoints.remove(breakpoint); if (removeFromStorage) this._storage._removeBreakpoint(breakpoint); }, /** * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint * @param {WebInspector.UILocation} uiLocation */ _uiLocationAdded: function(breakpoint, uiLocation) { var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); if (!breakpoints) { breakpoints = {}; this._breakpointsForUISourceCode.put(uiLocation.uiSourceCode, breakpoints); } var lineBreakpoints = breakpoints[uiLocation.lineNumber]; if (!lineBreakpoints) { lineBreakpoints = []; breakpoints[uiLocation.lineNumber] = lineBreakpoints; } lineBreakpoints.push(breakpoint); this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointAdded, {breakpoint: breakpoint, uiLocation: uiLocation}); }, /** * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint * @param {WebInspector.UILocation} uiLocation */ _uiLocationRemoved: function(breakpoint, uiLocation) { var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); if (!breakpoints) return; var lineBreakpoints = breakpoints[uiLocation.lineNumber]; if (!lineBreakpoints) return; lineBreakpoints.remove(breakpoint); if (!lineBreakpoints.length) delete breakpoints[uiLocation.lineNumber]; this.dispatchEventToListeners(WebInspector.BreakpointManager.Events.BreakpointRemoved, {breakpoint: breakpoint, uiLocation: uiLocation}); }, __proto__: WebInspector.Object.prototype } /** * @constructor * @param {WebInspector.BreakpointManager} breakpointManager * @param {WebInspector.UISourceCode} uiSourceCode * @param {number} lineNumber * @param {string} condition * @param {boolean} enabled */ WebInspector.BreakpointManager.Breakpoint = function(breakpointManager, uiSourceCode, lineNumber, condition, enabled) { this._breakpointManager = breakpointManager; this._primaryUILocation = new WebInspector.UILocation(uiSourceCode, lineNumber, 0); this._sourceFileId = WebInspector.BreakpointManager.sourceFileId(uiSourceCode); /** @type {Array.} */ this._liveLocations = []; /** @type {!Object.} */ this._uiLocations = {}; // Force breakpoint update. /** @type {string} */ this._condition; /** @type {boolean} */ this._enabled; this._updateBreakpoint(condition, enabled); } WebInspector.BreakpointManager.Breakpoint.prototype = { /** * @return {WebInspector.UILocation} */ primaryUILocation: function() { return this._primaryUILocation; }, /** * @param {WebInspector.DebuggerModel.Location} location */ _addResolvedLocation: function(location) { this._liveLocations.push(this._breakpointManager._debuggerModel.createLiveLocation(location, this._locationUpdated.bind(this, location))); }, /** * @param {WebInspector.DebuggerModel.Location} location * @param {WebInspector.UILocation} uiLocation */ _locationUpdated: function(location, uiLocation) { var stringifiedLocation = location.scriptId + ":" + location.lineNumber + ":" + location.columnNumber; var oldUILocation = /** @type {WebInspector.UILocation} */ (this._uiLocations[stringifiedLocation]); if (oldUILocation) this._breakpointManager._uiLocationRemoved(this, oldUILocation); if (this._uiLocations[""]) { delete this._uiLocations[""]; this._breakpointManager._uiLocationRemoved(this, this._primaryUILocation); } this._uiLocations[stringifiedLocation] = uiLocation; this._breakpointManager._uiLocationAdded(this, uiLocation); }, /** * @return {boolean} */ enabled: function() { return this._enabled; }, /** * @param {boolean} enabled */ setEnabled: function(enabled) { this._updateBreakpoint(this._condition, enabled); }, /** * @return {string} */ condition: function() { return this._condition; }, /** * @param {string} condition */ setCondition: function(condition) { this._updateBreakpoint(condition, this._enabled); }, /** * @param {string} condition * @param {boolean} enabled */ _updateBreakpoint: function(condition, enabled) { if (this._enabled === enabled && this._condition === condition) return; if (this._enabled) this._removeFromDebugger(); this._enabled = enabled; this._condition = condition; this._breakpointManager._storage._updateBreakpoint(this); var scriptFile = this._primaryUILocation.uiSourceCode.scriptFile(); if (this._enabled && !(scriptFile && scriptFile.hasDivergedFromVM())) { if (this._setInDebugger()) return; } this._fakeBreakpointAtPrimaryLocation(); }, /** * @param {boolean=} keepInStorage */ remove: function(keepInStorage) { var removeFromStorage = !keepInStorage; this._resetLocations(); this._removeFromDebugger(); this._breakpointManager._removeBreakpoint(this, removeFromStorage); }, _setInDebugger: function() { console.assert(!this._debuggerId); var rawLocation = this._primaryUILocation.uiLocationToRawLocation(); var debuggerModelLocation = /** @type {WebInspector.DebuggerModel.Location} */ (rawLocation); if (debuggerModelLocation) { this._breakpointManager._debuggerModel.setBreakpointByScriptLocation(debuggerModelLocation, this._condition, this._didSetBreakpointInDebugger.bind(this)); return true; } if (this._primaryUILocation.uiSourceCode.url) { this._breakpointManager._debuggerModel.setBreakpointByURL(this._primaryUILocation.uiSourceCode.url, this._primaryUILocation.lineNumber, 0, this._condition, this._didSetBreakpointInDebugger.bind(this)); return true; } return false; }, /** * @this {WebInspector.BreakpointManager.Breakpoint} * @param {?DebuggerAgent.BreakpointId} breakpointId * @param {Array.} locations */ _didSetBreakpointInDebugger: function(breakpointId, locations) { if (!breakpointId) { this._resetLocations(); this._breakpointManager._removeBreakpoint(this, false); return; } this._debuggerId = breakpointId; this._breakpointManager._breakpointForDebuggerId[breakpointId] = this; if (!locations.length) { this._fakeBreakpointAtPrimaryLocation(); return; } this._resetLocations(); for (var i = 0; i < locations.length; ++i) { var script = this._breakpointManager._debuggerModel.scriptForId(locations[i].scriptId); var uiLocation = script.rawLocationToUILocation(locations[i].lineNumber, locations[i].columnNumber); if (this._breakpointManager.findBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber)) { // location clash this.remove(); return; } } for (var i = 0; i < locations.length; ++i) this._addResolvedLocation(locations[i]); }, _removeFromDebugger: function() { if (this._debuggerId) { this._breakpointManager._debuggerModel.removeBreakpoint(this._debuggerId); delete this._breakpointManager._breakpointForDebuggerId[this._debuggerId]; delete this._debuggerId; } }, _resetLocations: function() { for (var stringifiedLocation in this._uiLocations) this._breakpointManager._uiLocationRemoved(this, this._uiLocations[stringifiedLocation]); for (var i = 0; i < this._liveLocations.length; ++i) this._liveLocations[i].dispose(); this._liveLocations = []; this._uiLocations = {}; }, /** * @return {string} */ _breakpointStorageId: function() { if (!this._sourceFileId) return ""; return this._sourceFileId + ":" + this._primaryUILocation.lineNumber; }, _fakeBreakpointAtPrimaryLocation: function() { this._resetLocations(); this._uiLocations[""] = this._primaryUILocation; this._breakpointManager._uiLocationAdded(this, this._primaryUILocation); } } /** * @constructor * @param {WebInspector.BreakpointManager} breakpointManager * @param {WebInspector.Setting} setting */ WebInspector.BreakpointManager.Storage = function(breakpointManager, setting) { this._breakpointManager = breakpointManager; this._setting = setting; var breakpoints = this._setting.get(); /** @type {Object.} */ this._breakpoints = {}; for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = /** @type {WebInspector.BreakpointManager.Storage.Item} */ (breakpoints[i]); this._breakpoints[breakpoint.sourceFileId + ":" + breakpoint.lineNumber] = breakpoint; } } WebInspector.BreakpointManager.Storage.prototype = { /** * @param {WebInspector.UISourceCode} uiSourceCode */ _restoreBreakpoints: function(uiSourceCode) { this._muted = true; var sourceFileId = WebInspector.BreakpointManager.sourceFileId(uiSourceCode); for (var id in this._breakpoints) { var breakpoint = this._breakpoints[id]; if (breakpoint.sourceFileId === sourceFileId) this._breakpointManager._innerSetBreakpoint(uiSourceCode, breakpoint.lineNumber, breakpoint.condition, breakpoint.enabled); } delete this._muted; }, /** * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint */ _updateBreakpoint: function(breakpoint) { if (this._muted || !breakpoint._breakpointStorageId()) return; this._breakpoints[breakpoint._breakpointStorageId()] = new WebInspector.BreakpointManager.Storage.Item(breakpoint); this._save(); }, /** * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint */ _removeBreakpoint: function(breakpoint) { if (this._muted) return; delete this._breakpoints[breakpoint._breakpointStorageId()]; this._save(); }, _save: function() { var breakpointsArray = []; for (var id in this._breakpoints) breakpointsArray.push(this._breakpoints[id]); this._setting.set(breakpointsArray); } } /** * @constructor * @param {WebInspector.BreakpointManager.Breakpoint} breakpoint */ WebInspector.BreakpointManager.Storage.Item = function(breakpoint) { var primaryUILocation = breakpoint.primaryUILocation(); this.sourceFileId = breakpoint._sourceFileId; this.lineNumber = primaryUILocation.lineNumber; this.condition = breakpoint.condition(); this.enabled = breakpoint.enabled(); } /** @type {WebInspector.BreakpointManager} */ WebInspector.breakpointManager = null;