diff options
Diffstat (limited to 'Source/WebCore/inspector/front-end/DebuggerPresentationModel.js')
-rw-r--r-- | Source/WebCore/inspector/front-end/DebuggerPresentationModel.js | 547 |
1 files changed, 497 insertions, 50 deletions
diff --git a/Source/WebCore/inspector/front-end/DebuggerPresentationModel.js b/Source/WebCore/inspector/front-end/DebuggerPresentationModel.js index a97db34..d12affe 100644 --- a/Source/WebCore/inspector/front-end/DebuggerPresentationModel.js +++ b/Source/WebCore/inspector/front-end/DebuggerPresentationModel.js @@ -30,102 +30,549 @@ WebInspector.DebuggerPresentationModel = function() { - this._breakpoints = {}; - this._sourceLocationToBreakpointId = {}; + this._sourceFiles = {}; + this._messages = []; + this._presentationBreakpoints = {}; + this._presentationCallFrames = []; + this._selectedCallFrameIndex = 0; - WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointAdded, this._breakpointAdded, this); - WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointRemoved, this._breakpointRemoved, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasEnabled, this._debuggerWasEnabled, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._failedToParseScriptSource, this); WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointResolved, this._breakpointResolved, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.Reset, this._reset, this); } WebInspector.DebuggerPresentationModel.Events = { + SourceFileAdded: "source-file-added", + SourceFileChanged: "source-file-changed", + ConsoleMessageAdded: "console-message-added", BreakpointAdded: "breakpoint-added", - BreakpointRemoved: "breakpoint-removed" + BreakpointRemoved: "breakpoint-removed", + DebuggerPaused: "debugger-paused", + DebuggerResumed: "debugger-resumed", + CallFrameSelected: "call-frame-selected" } WebInspector.DebuggerPresentationModel.prototype = { + _debuggerWasEnabled: function() + { + this._restoreBreakpoints(); + }, + + sourceFile: function(sourceFileId) + { + return this._sourceFiles[sourceFileId]; + }, + + requestSourceFileContent: function(sourceFileId, callback) + { + this._sourceFiles[sourceFileId].requestContent(callback); + }, + + _parsedScriptSource: function(event) + { + this._addScript(event.data); + this._refreshBreakpoints(); + }, + + _failedToParseScriptSource: function(event) + { + this._addScript(event.data); + this._refreshBreakpoints(); + }, + + _addScript: function(script) + { + var sourceFileId = script.sourceURL || script.sourceID; + var sourceFile = this._sourceFiles[sourceFileId]; + if (sourceFile) { + sourceFile.addScript(script); + return; + } + + function contentChanged(sourceFile) + { + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.SourceFileChanged, this._sourceFiles[sourceFileId]); + } + if (!this._formatSourceFiles) + sourceFile = new WebInspector.SourceFile(sourceFileId, script, contentChanged.bind(this)); + else + sourceFile = new WebInspector.FormattedSourceFile(sourceFileId, script, contentChanged.bind(this), this._formatter); + this._sourceFiles[sourceFileId] = sourceFile; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.SourceFileAdded, sourceFile); + }, + + _refreshBreakpoints: function() + { + var breakpoints = WebInspector.debuggerModel.breakpoints; + for (var id in breakpoints) { + if (!(id in this._presentationBreakpoints)) + this._breakpointAdded(breakpoints[id]); + } + }, + + canEditScriptSource: function(sourceFileId) + { + if (!Preferences.canEditScriptSource) + return false; + var script = this._scriptForSourceFileId(sourceFileId); + return !script.lineOffset && !script.columnOffset; + }, + + editScriptSource: function(sourceFileId, text, callback) + { + var script = this._scriptForSourceFileId(sourceFileId); + var sourceFile = this._sourceFiles[sourceFileId]; + var oldSource = sourceFile.content; + function didEditScriptSource(success, newBodyOrErrorMessage) + { + if (!success) { + callback(false, newBodyOrErrorMessage); + return; + } + + var newSource = newBodyOrErrorMessage; + this._updateBreakpointsAfterLiveEdit(sourceFileId, oldSource, newSource); + + var resource = WebInspector.resourceForURL(script.sourceURL); + if (resource) { + var revertHandle = this.editScriptSource.bind(this, sourceFileId, oldSource, sourceFile.reload.bind(sourceFile)); + resource.setContent(newSource, revertHandle); + } + + callback(true, newSource); + + if (WebInspector.debuggerModel.callFrames) + this._debuggerPaused(); + } + WebInspector.debuggerModel.editScriptSource(script.sourceID, text, didEditScriptSource.bind(this)); + }, + + _updateBreakpointsAfterLiveEdit: function(sourceFileId, oldSource, newSource) + { + // Clear and re-create breakpoints according to text diff. + var diff = Array.diff(oldSource.split("\n"), newSource.split("\n")); + for (var id in this._presentationBreakpoints) { + var breakpoint = this._presentationBreakpoints[id]; + if (breakpoint.sourceFileId !== sourceFileId) + continue; + var lineNumber = breakpoint.lineNumber; + this.removeBreakpoint(sourceFileId, 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) + this.setBreakpoint(sourceFileId, newLineNumber, breakpoint.condition, breakpoint.enabled); + } + }, + + toggleFormatSourceFiles: function() + { + this._formatSourceFiles = !this._formatSourceFiles; + if (this._formatSourceFiles && !this._formatter) + this._formatter = new WebInspector.ScriptFormatter(); + + var messages = this._messages; + this._sourceFiles = {}; + this._messages = []; + this._presentationBreakpoints = {}; + + var scripts = WebInspector.debuggerModel.scripts; + for (var id in scripts) + this._addScript(scripts[id]); + + for (var i = 0; i < messages.length; ++i) + this.addConsoleMessage(messages[i]); + + this._refreshBreakpoints(); + + if (WebInspector.debuggerModel.callFrames) + this._debuggerPaused(); + }, + + addConsoleMessage: function(message) + { + this._messages.push(message); + + var sourceFile = this._sourceFileForScriptURL(message.url); + if (!sourceFile) + return; + + function didRequestSourceMapping(mapping) + { + var presentationMessage = {}; + presentationMessage.sourceFileId = sourceFile.id; + presentationMessage.lineNumber = mapping.scriptLocationToSourceLocation(message.line - 1, 0).lineNumber; + presentationMessage.originalMessage = message; + sourceFile.messages.push(presentationMessage); + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, presentationMessage); + } + sourceFile.requestSourceMapping(didRequestSourceMapping.bind(this)); + }, + + clearConsoleMessages: function() + { + this._messages = []; + for (var id in this._sourceFiles) + this._sourceFiles[id].messages = []; + }, + + continueToLine: function(sourceFileId, lineNumber) + { + function didRequestSourceMapping(mapping) + { + var location = mapping.sourceLocationToScriptLocation(lineNumber, 0); + WebInspector.debuggerModel.continueToLocation(location.scriptId, location.lineNumber, location.columnNumber); + } + this._sourceFiles[sourceFileId].requestSourceMapping(didRequestSourceMapping.bind(this)); + }, + breakpointsForSourceFileId: function(sourceFileId) { + var sourceFile = this.sourceFile(sourceFileId); + if (!sourceFile) + return []; var breakpoints = []; - for (var id in this._breakpoints) { - var breakpoint = this._breakpoints[id]; - if (breakpoint.sourceFileId === sourceFileId) - breakpoints.push(breakpoint); - } + for (var lineNumber in sourceFile.breakpoints) + breakpoints.push(sourceFile.breakpoints[lineNumber]); return breakpoints; }, - _breakpointAdded: function(event) + setBreakpoint: function(sourceFileId, lineNumber, condition, enabled) { - var breakpoint = event.data; - var location = breakpoint.locations.length ? breakpoint.locations[0] : breakpoint; - var sourceLocation = this._actualLocationToSourceLocation(breakpoint.url || breakpoint.sourceID, location.lineNumber, location.columnNumber); + function didSetBreakpoint(breakpoint) + { + if (breakpoint) { + this._breakpointAdded(breakpoint); + this._saveBreakpoints(); + } + } - var encodedSourceLocation = this._encodeSourceLocation(sourceLocation.sourceFileId, sourceLocation.lineNumber); - if (encodedSourceLocation in this._sourceLocationToBreakpointId) { - // We can't show more than one breakpoint on a single source frame line. Remove newly added breakpoint. - WebInspector.debuggerModel.removeBreakpoint(breakpoint.id); - return; + function didRequestSourceMapping(mapping) + { + var location = mapping.sourceLocationToScriptLocation(lineNumber, 0); + var script = WebInspector.debuggerModel.scriptForSourceID(location.scriptId); + if (script.sourceURL) + WebInspector.debuggerModel.setBreakpoint(script.sourceURL, location.lineNumber, location.columnNumber, condition, enabled, didSetBreakpoint.bind(this)); + else + WebInspector.debuggerModel.setBreakpointBySourceId(script.sourceID, location.lineNumber, location.columnNumber, condition, enabled, didSetBreakpoint.bind(this)); } + this._sourceFiles[sourceFileId].requestSourceMapping(didRequestSourceMapping.bind(this)); + }, - var presentationBreakpoint = { - sourceFileId: sourceLocation.sourceFileId, - lineNumber: sourceLocation.lineNumber, - url: breakpoint.url, - resolved: !!breakpoint.locations.length, - condition: breakpoint.condition, - enabled: breakpoint.enabled - }; + setBreakpointEnabled: function(sourceFileId, lineNumber, enabled) + { + var breakpoint = this.removeBreakpoint(sourceFileId, lineNumber); + this.setBreakpoint(sourceFileId, lineNumber, breakpoint.condition, enabled); + }, - this._sourceLocationToBreakpointId[encodedSourceLocation] = breakpoint.id; - this._breakpoints[breakpoint.id] = presentationBreakpoint; + updateBreakpoint: function(sourceFileId, lineNumber, condition, enabled) + { + this.removeBreakpoint(sourceFileId, lineNumber); + this.setBreakpoint(sourceFileId, lineNumber, condition, enabled); + }, - this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, presentationBreakpoint); + removeBreakpoint: function(sourceFileId, lineNumber) + { + var breakpoint = this.findBreakpoint(sourceFileId, lineNumber); + WebInspector.debuggerModel.removeBreakpoint(breakpoint._id); + this._breakpointRemoved(breakpoint._id); + this._saveBreakpoints(); + return breakpoint; + }, + + findBreakpoint: function(sourceFileId, lineNumber) + { + var sourceFile = this.sourceFile(sourceFileId); + if (sourceFile) + return sourceFile.breakpoints[lineNumber]; }, - _breakpointRemoved: function(event) + _breakpointAdded: function(breakpoint) { - var breakpointId = event.data; - var breakpoint = this._breakpoints[breakpointId]; - var encodedSourceLocation = this._encodeSourceLocation(breakpoint.sourceFileId, breakpoint.lineNumber); - delete this._breakpoints[breakpointId]; - delete this._sourceLocationToBreakpointId[encodedSourceLocation]; + var script; + if (breakpoint.url) + script = WebInspector.debuggerModel.scriptsForURL(breakpoint.url)[0]; + else + script = WebInspector.debuggerModel.scriptForSourceID(breakpoint.sourceID); + if (!script) + return; + + function didRequestSourceMapping(mapping) + { + var scriptLocation = breakpoint.locations.length ? breakpoint.locations[0] : breakpoint; + var sourceLocation = mapping.scriptLocationToSourceLocation(scriptLocation.lineNumber, scriptLocation.columnNumber); + var lineNumber = sourceLocation.lineNumber; + + if (this.findBreakpoint(sourceFile.id, lineNumber)) { + // We can't show more than one breakpoint on a single source file line. + WebInspector.debuggerModel.removeBreakpoint(breakpoint.id); + return; + } + + var presentationBreakpoint = new WebInspector.PresentationBreakpoint(breakpoint, sourceFile, lineNumber); + presentationBreakpoint._id = breakpoint.id; + this._presentationBreakpoints[breakpoint.id] = presentationBreakpoint; + sourceFile.breakpoints[lineNumber] = presentationBreakpoint; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, presentationBreakpoint); + } + var sourceFile = this._sourceFileForScript(script); + sourceFile.requestSourceMapping(didRequestSourceMapping.bind(this)); + }, + + _breakpointRemoved: function(breakpointId) + { + var breakpoint = this._presentationBreakpoints[breakpointId]; + delete this._presentationBreakpoints[breakpointId]; + var sourceFile = this.sourceFile(breakpoint.sourceFileId); + delete sourceFile.breakpoints[breakpoint.lineNumber]; this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, breakpoint); }, _breakpointResolved: function(event) { var breakpoint = event.data; - this._breakpointRemoved({ data: breakpoint.id }); - this._breakpointAdded({ data: breakpoint }); + if (!(breakpoint.id in this._presentationBreakpoints)) + return; + this._breakpointRemoved(breakpoint.id); + this._breakpointAdded(breakpoint); + }, + + _restoreBreakpoints: function() + { + function didSetBreakpoint(breakpoint) + { + if (breakpoint) + this._breakpointAdded(breakpoint); + } + var breakpoints = WebInspector.settings.breakpoints; + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + WebInspector.debuggerModel.setBreakpoint(breakpoint.url, breakpoint.lineNumber, breakpoint.columnNumber, breakpoint.condition, breakpoint.enabled, didSetBreakpoint.bind(this)); + } }, - _encodeSourceLocation: function(sourceFileId, lineNumber) + _saveBreakpoints: function() { - return sourceFileId + ":" + lineNumber; + var serializedBreakpoints = []; + var breakpoints = WebInspector.debuggerModel.breakpoints; + for (var id in breakpoints) { + var breakpoint = 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; }, - set selectedCallFrame(callFrame) + _debuggerPaused: function() { - this._selectedCallFrame = callFrame; - if (!callFrame) - return; + var callFrames = WebInspector.debuggerModel.callFrames; + this._presentationCallFrames = []; + for (var i = 0; i < callFrames.length; ++i) { + var callFrame = callFrames[i]; + var sourceFile; + var script = WebInspector.debuggerModel.scriptForSourceID(callFrame.sourceID); + if (script) + sourceFile = this._sourceFileForScript(script); + this._presentationCallFrames.push(new WebInspector.PresenationCallFrame(callFrame, i, sourceFile)); + } + var details = WebInspector.debuggerModel.debuggerPausedDetails; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, { callFrames: this._presentationCallFrames, details: details }); + + this.selectedCallFrame = this._presentationCallFrames[this._selectedCallFrameIndex]; + }, + + _debuggerResumed: function() + { + this._presentationCallFrames = []; + this._selectedCallFrameIndex = 0; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed); + }, - var script = WebInspector.debuggerModel.scriptForSourceID(callFrame.sourceID); - callFrame.sourceLocation = this._actualLocationToSourceLocation(script.sourceURL || script.sourceID, callFrame.line, callFrame.column); + set selectedCallFrame(callFrame) + { + this._selectedCallFrameIndex = callFrame.index; + callFrame.select(); this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, callFrame); }, get selectedCallFrame() { - return this._selectedCallFrame; + return this._presentationCallFrames[this._selectedCallFrameIndex]; + }, + + _sourceFileForScript: function(script) + { + return this._sourceFiles[script.sourceURL || script.sourceID]; + }, + + _sourceFileForScriptURL: function(scriptURL) + { + return this._sourceFiles[scriptURL]; + }, + + _scriptForSourceFileId: function(sourceFileId) + { + function filter(script) + { + return (script.sourceURL || script.sourceID) === sourceFileId; + } + return WebInspector.debuggerModel.queryScripts(filter)[0]; }, - _actualLocationToSourceLocation: function(sourceID, lineNumber, columnNumber) + _reset: function() { - // TODO: use source mapping to obtain source location. - return { sourceFileId: sourceID, lineNumber: lineNumber, columnNumber: columnNumber }; + this._sourceFiles = {}; + this._messages = []; + this._presentationBreakpoints = {}; + this._presentationCallFrames = []; + this._selectedCallFrameIndex = 0; } } WebInspector.DebuggerPresentationModel.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.PresentationBreakpoint = function(breakpoint, sourceFile, lineNumber) +{ + this._breakpoint = breakpoint; + this._sourceFile = sourceFile; + this._lineNumber = lineNumber; +} + +WebInspector.PresentationBreakpoint.prototype = { + get sourceFileId() + { + return this._sourceFile.id; + }, + + get lineNumber() + { + return this._lineNumber; + }, + + get condition() + { + return this._breakpoint.condition; + }, + + get enabled() + { + return this._breakpoint.enabled; + }, + + get url() + { + return this._sourceFile.url; + }, + + get resolved() + { + return !!this._breakpoint.locations.length + }, + + loadSnippet: function(callback) + { + function didRequestContent(mimeType, content) + { + var lineEndings = content.lineEndings(); + var snippet = ""; + if (this.lineNumber < lineEndings.length) + snippet = content.substring(lineEndings[this.lineNumber - 1], lineEndings[this.lineNumber]); + callback(snippet); + } + this._sourceFile.requestContent(didRequestContent.bind(this)); + } +} + +WebInspector.PresenationCallFrame = function(callFrame, index, sourceFile) +{ + this._callFrame = callFrame; + this._index = index; + this._sourceFile = sourceFile; + this._script = WebInspector.debuggerModel.scriptForSourceID(callFrame.sourceID); +} + +WebInspector.PresenationCallFrame.prototype = { + get functionName() + { + return this._callFrame.functionName; + }, + + get type() + { + return this._callFrame.type; + }, + + get isInternalScript() + { + return !this._script; + }, + + get url() + { + if (this._sourceFile) + return this._sourceFile.url; + }, + + get scopeChain() + { + return this._callFrame.scopeChain; + }, + + get index() + { + return this._index; + }, + + select: function() + { + if (this._sourceFile) + this._sourceFile.forceLoadContent(this._script); + }, + + evaluate: function(code, objectGroup, includeCommandLineAPI, callback) + { + function didEvaluateOnCallFrame(error, result) + { + callback(WebInspector.RemoteObject.fromPayload(result)); + } + DebuggerAgent.evaluateOnCallFrame(this._callFrame.id, code, objectGroup, includeCommandLineAPI, didEvaluateOnCallFrame.bind(this)); + }, + + sourceLocation: function(callback) + { + if (!this._sourceFile) { + callback(undefined, this._callFrame.line, this._callFrame.column); + return; + } + + function didRequestSourceMapping(mapping) + { + var sourceLocation = mapping.scriptLocationToSourceLocation(this._callFrame.line, this._callFrame.column); + callback(this._sourceFile.id, sourceLocation.lineNumber, sourceLocation.columnNumber); + } + this._sourceFile.requestSourceMapping(didRequestSourceMapping.bind(this)); + } +} |