/* * Copyright (C) 2010 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. */ WebInspector.DebuggerModel = function() { this._paused = false; this._callFrames = []; this._breakpoints = {}; this._scripts = {}; InspectorBackend.registerDomainDispatcher("Debugger", new WebInspector.DebuggerDispatcher(this)); } WebInspector.DebuggerModel.Events = { DebuggerPaused: "debugger-paused", DebuggerResumed: "debugger-resumed", ParsedScriptSource: "parsed-script-source", FailedToParseScriptSource: "failed-to-parse-script-source", ScriptSourceChanged: "script-source-changed", BreakpointAdded: "breakpoint-added", BreakpointRemoved: "breakpoint-removed", BreakpointResolved: "breakpoint-resolved" } WebInspector.DebuggerModel.prototype = { enableDebugger: function() { InspectorBackend.enableDebugger(); if (this._breakpointsPushedToBackend) return; var breakpoints = WebInspector.settings.breakpoints; for (var i = 0; i < breakpoints.length; ++i) { var breakpoint = breakpoints[i]; if (typeof breakpoint.url !== "string" || typeof breakpoint.lineNumber !== "number" || typeof breakpoint.columnNumber !== "number" || typeof breakpoint.condition !== "string" || typeof breakpoint.enabled !== "boolean") continue; this.setBreakpoint(breakpoint.url, breakpoint.lineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled); } this._breakpointsPushedToBackend = true; }, disableDebugger: function() { InspectorBackend.disableDebugger(); }, continueToLocation: function(sourceID, lineNumber, columnNumber) { InspectorBackend.continueToLocation(sourceID, lineNumber, columnNumber); }, setBreakpoint: function(url, lineNumber, columnNumber, condition, enabled) { function didSetBreakpoint(breakpointsPushedToBackend, breakpointId, locations) { if (!breakpointId) return; var breakpoint = new WebInspector.Breakpoint(breakpointId, url, "", lineNumber, columnNumber, condition, enabled); breakpoint.locations = locations; this._breakpoints[breakpointId] = breakpoint; if (breakpointsPushedToBackend) this._saveBreakpoints(); this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpoint); } InspectorBackend.setJavaScriptBreakpoint(url, lineNumber, columnNumber, condition, enabled, didSetBreakpoint.bind(this, this._breakpointsPushedToBackend)); }, setBreakpointBySourceId: function(sourceID, lineNumber, columnNumber, condition, enabled) { function didSetBreakpoint(breakpointId, actualLineNumber, actualColumnNumber) { if (!breakpointId) return; var breakpoint = new WebInspector.Breakpoint(breakpointId, "", sourceID, lineNumber, columnNumber, condition, enabled); breakpoint.addLocation(sourceID, actualLineNumber, actualColumnNumber); this._breakpoints[breakpointId] = breakpoint; this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpoint); } InspectorBackend.setJavaScriptBreakpointBySourceId(sourceID, lineNumber, columnNumber, condition, enabled, didSetBreakpoint.bind(this)); }, removeBreakpoint: function(breakpointId) { InspectorBackend.removeJavaScriptBreakpoint(breakpointId); var breakpoint = this._breakpoints[breakpointId]; delete this._breakpoints[breakpointId]; this._saveBreakpoints(); this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointRemoved, breakpointId); }, updateBreakpoint: function(breakpointId, condition, enabled) { var breakpoint = this._breakpoints[breakpointId]; this.removeBreakpoint(breakpointId); if (breakpoint.url) this.setBreakpoint(breakpoint.url, breakpoint.lineNumber, breakpoint.columnNumber, condition, enabled); else this.setBreakpointBySourceId(breakpoint.sourceID, breakpoint.lineNumber, breakpoint.columnNumber, condition, enabled); }, _breakpointResolved: function(breakpointId, sourceID, lineNumber, columnNumber) { var breakpoint = this._breakpoints[breakpointId]; if (!breakpoint) return; breakpoint.addLocation(sourceID, lineNumber, columnNumber); this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointResolved, breakpoint); }, _saveBreakpoints: function() { var serializedBreakpoints = []; for (var id in this._breakpoints) { var breakpoint = this._breakpoints[id]; if (!breakpoint.url) continue; var serializedBreakpoint = {}; serializedBreakpoint.url = breakpoint.url; serializedBreakpoint.lineNumber = breakpoint.lineNumber; serializedBreakpoint.columnNumber = breakpoint.columnNumber; serializedBreakpoint.condition = breakpoint.condition; serializedBreakpoint.enabled = breakpoint.enabled; serializedBreakpoints.push(serializedBreakpoint); } WebInspector.settings.breakpoints = serializedBreakpoints; }, get breakpoints() { return this._breakpoints; }, breakpointForId: function(breakpointId) { return this._breakpoints[breakpointId]; }, queryBreakpoints: function(filter) { var breakpoints = []; for (var id in this._breakpoints) { var breakpoint = this._breakpoints[id]; if (filter(breakpoint)) breakpoints.push(breakpoint); } return breakpoints; }, reset: function() { this._paused = false; this._callFrames = []; for (var id in this._breakpoints) { var breakpoint = this._breakpoints[id]; if (!breakpoint.url) this.removeBreakpoint(id); else breakpoint.locations = []; } this._scripts = {}; }, scriptForSourceID: function(sourceID) { return this._scripts[sourceID]; }, scriptsForURL: function(url) { return this.queryScripts(function(s) { return s.sourceURL === url; }); }, queryScripts: function(filter) { var scripts = []; for (var sourceID in this._scripts) { var script = this._scripts[sourceID]; if (filter(script)) scripts.push(script); } return scripts; }, editScriptSource: function(sourceID, scriptSource) { function didEditScriptSource(success, newBodyOrErrorMessage, callFrames) { if (success) { if (callFrames && callFrames.length) this._callFrames = callFrames; this._updateScriptSource(sourceID, newBodyOrErrorMessage); } else WebInspector.log(newBodyOrErrorMessage, WebInspector.ConsoleMessage.MessageLevel.Warning); } InspectorBackend.editScriptSource(sourceID, scriptSource, didEditScriptSource.bind(this)); }, _updateScriptSource: function(sourceID, scriptSource) { var script = this._scripts[sourceID]; var oldSource = script.source; script.source = scriptSource; // Clear and re-create breakpoints according to text diff. var diff = Array.diff(oldSource.split("\n"), script.source.split("\n")); for (var id in this._breakpoints) { var breakpoint = this._breakpoints[id]; if (breakpoint.url) { if (breakpoint.url !== script.sourceURL) continue; } else { if (breakpoint.sourceID !== sourceID) continue; } this.removeBreakpoint(breakpoint.id); var lineNumber = breakpoint.lineNumber; var newLineNumber = diff.left[lineNumber].row; if (newLineNumber === undefined) { for (var i = lineNumber - 1; i >= 0; --i) { if (diff.left[i].row === undefined) continue; var shiftedLineNumber = diff.left[i].row + lineNumber - i; if (shiftedLineNumber < diff.right.length) { var originalLineNumber = diff.right[shiftedLineNumber].row; if (originalLineNumber === lineNumber || originalLineNumber === undefined) newLineNumber = shiftedLineNumber; } break; } } if (newLineNumber === undefined) continue; if (breakpoint.url) this.setBreakpoint(breakpoint.url, newLineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled); else this.setBreakpointBySourceId(sourceID, newLineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled); } this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ScriptSourceChanged, { sourceID: sourceID, oldSource: oldSource }); }, get callFrames() { return this._callFrames; }, _pausedScript: function(details) { this._paused = true; this._callFrames = details.callFrames; details.breakpoint = this._breakpointForCallFrame(details.callFrames[0]); this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerPaused, details); }, _resumedScript: function() { this._paused = false; this._callFrames = []; this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerResumed); }, _breakpointForCallFrame: function(callFrame) { function match(location) { if (location.sourceID != callFrame.sourceID) return false; return location.lineNumber === callFrame.line && location.columnNumber === callFrame.column; } for (var id in this._breakpoints) { var breakpoint = this._breakpoints[id]; for (var i = 0; i < breakpoint.locations.length; ++i) { if (match(breakpoint.locations[i])) return breakpoint; } } }, _parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType) { var script = new WebInspector.Script(sourceID, sourceURL, "", lineOffset, columnOffset, length, undefined, undefined, scriptWorldType); this._scripts[sourceID] = script; this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script); }, _failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage) { var script = new WebInspector.Script(null, sourceURL, source, startingLine, errorLine, errorMessage, undefined); this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, script); } } WebInspector.DebuggerModel.prototype.__proto__ = WebInspector.Object.prototype; WebInspector.DebuggerEventTypes = { JavaScriptPause: 0, JavaScriptBreakpoint: 1, NativeBreakpoint: 2 }; WebInspector.DebuggerDispatcher = function(debuggerModel) { this._debuggerModel = debuggerModel; } WebInspector.DebuggerDispatcher.prototype = { pausedScript: function(details) { this._debuggerModel._pausedScript(details); }, resumedScript: function() { this._debuggerModel._resumedScript(); }, debuggerWasEnabled: function() { WebInspector.panels.scripts.debuggerWasEnabled(); }, debuggerWasDisabled: function() { WebInspector.panels.scripts.debuggerWasDisabled(); }, parsedScriptSource: function(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType) { this._debuggerModel._parsedScriptSource(sourceID, sourceURL, lineOffset, columnOffset, length, scriptWorldType); }, failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage) { this._debuggerModel._failedToParseScriptSource(sourceURL, source, startingLine, errorLine, errorMessage); }, breakpointResolved: function(breakpointId, sourceID, lineNumber, columnNumber) { this._debuggerModel._breakpointResolved(breakpointId, sourceID, lineNumber, columnNumber); }, didCreateWorker: function() { var workersPane = WebInspector.panels.scripts.sidebarPanes.workers; workersPane.addWorker.apply(workersPane, arguments); }, didDestroyWorker: function() { var workersPane = WebInspector.panels.scripts.sidebarPanes.workers; workersPane.removeWorker.apply(workersPane, arguments); } }