2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 WebInspector.DebuggerPresentationModel = function()
36 // FIXME: apply formatter from outside as a generic mapping.
37 this._formatter = new WebInspector.ScriptFormatter();
38 this._rawSourceCode = {};
39 this._presentationCallFrames = [];
40 this._selectedCallFrameIndex = 0;
42 this._breakpointManager = new WebInspector.BreakpointManager(WebInspector.settings.breakpoints, this._breakpointAdded.bind(this), this._breakpointRemoved.bind(this), WebInspector.debuggerModel);
44 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this);
45 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._failedToParseScriptSource, this);
46 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this);
47 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this);
48 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.Reset, this._debuggerReset, this);
50 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
51 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
53 new WebInspector.DebuggerPresentationModelResourceBinding(this);
56 WebInspector.DebuggerPresentationModel.Events = {
57 UISourceCodeAdded: "source-file-added",
58 UISourceCodeReplaced: "source-file-replaced",
59 ConsoleMessageAdded: "console-message-added",
60 ConsoleMessagesCleared: "console-messages-cleared",
61 BreakpointAdded: "breakpoint-added",
62 BreakpointRemoved: "breakpoint-removed",
63 DebuggerPaused: "debugger-paused",
64 DebuggerResumed: "debugger-resumed",
65 CallFrameSelected: "call-frame-selected"
68 WebInspector.DebuggerPresentationModel.prototype = {
69 linkifyLocation: function(sourceURL, lineNumber, columnNumber, classes)
71 var linkText = WebInspector.formatLinkText(sourceURL, lineNumber);
72 var anchor = WebInspector.linkifyURLAsNode(sourceURL, linkText, classes, false);
74 var rawSourceCode = this._rawSourceCodeForScript(sourceURL);
76 anchor.setAttribute("preferred_panel", "resources");
77 anchor.setAttribute("line_number", lineNumber);
81 function updateAnchor()
83 var uiLocation = rawSourceCode.rawLocationToUILocation({ lineNumber: lineNumber, columnNumber: columnNumber });
84 anchor.textContent = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber);
85 anchor.setAttribute("preferred_panel", "scripts");
86 anchor.uiSourceCode = uiLocation.uiSourceCode;
87 anchor.lineNumber = uiLocation.lineNumber;
89 if (rawSourceCode.uiSourceCode)
90 updateAnchor.call(this);
91 rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, updateAnchor, this);
95 _parsedScriptSource: function(event)
97 this._addScript(event.data);
100 _failedToParseScriptSource: function(event)
102 this._addScript(event.data);
105 _addScript: function(script)
107 var rawSourceCodeId = this._createRawSourceCodeId(script.sourceURL, script.scriptId);
108 var rawSourceCode = this._rawSourceCode[rawSourceCodeId];
110 rawSourceCode.addScript(script);
115 if (script.sourceURL)
116 resource = WebInspector.networkManager.inflightResourceForURL(script.sourceURL) || WebInspector.resourceForURL(script.sourceURL);
117 rawSourceCode = new WebInspector.RawSourceCode(rawSourceCodeId, script, resource, this._formatter, this._formatSource);
118 this._rawSourceCode[rawSourceCodeId] = rawSourceCode;
119 if (rawSourceCode.uiSourceCode)
120 this._updateSourceMapping(rawSourceCode, null);
121 rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._sourceMappingUpdated, this);
124 _sourceMappingUpdated: function(event)
126 var rawSourceCode = event.target;
127 var oldUISourceCode = event.data.oldUISourceCode;
128 this._updateSourceMapping(rawSourceCode, oldUISourceCode);
131 _updateSourceMapping: function(rawSourceCode, oldUISourceCode)
133 var uiSourceCode = rawSourceCode.uiSourceCode;
135 if (oldUISourceCode) {
136 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(oldUISourceCode);
137 for (var lineNumber in breakpoints) {
138 var breakpoint = breakpoints[lineNumber];
139 this._breakpointRemoved(breakpoint);
140 delete breakpoint.uiSourceCode;
144 this._restoreBreakpoints(uiSourceCode);
145 this._restoreConsoleMessages(uiSourceCode);
147 if (!oldUISourceCode)
148 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeAdded, uiSourceCode);
150 var eventData = { uiSourceCode: uiSourceCode, oldUISourceCode: oldUISourceCode };
151 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeReplaced, eventData);
155 _restoreBreakpoints: function(uiSourceCode)
157 this._breakpointManager.uiSourceCodeAdded(uiSourceCode);
158 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
159 for (var lineNumber in breakpoints)
160 this._breakpointAdded(breakpoints[lineNumber]);
163 _restoreConsoleMessages: function(uiSourceCode)
165 var messages = uiSourceCode.rawSourceCode.messages;
166 for (var i = 0; i < messages.length; ++i)
167 messages[i]._presentationMessage = this._createPresentationMessage(messages[i], uiSourceCode);
170 canEditScriptSource: function(uiSourceCode)
172 if (!Preferences.canEditScriptSource || this._formatSource)
174 var rawSourceCode = uiSourceCode.rawSourceCode;
175 var script = this._scriptForRawSourceCode(rawSourceCode);
176 return !script.lineOffset && !script.columnOffset;
179 setScriptSource: function(uiSourceCode, newSource, callback)
181 var rawSourceCode = uiSourceCode.rawSourceCode;
182 var script = this._scriptForRawSourceCode(rawSourceCode);
184 function didEditScriptSource(error)
190 var resource = WebInspector.resourceForURL(rawSourceCode.url);
192 resource.addRevision(newSource);
194 rawSourceCode.contentEdited();
196 if (WebInspector.debuggerModel.callFrames)
197 this._debuggerPaused();
199 WebInspector.debuggerModel.setScriptSource(script.scriptId, newSource, didEditScriptSource.bind(this));
202 _updateBreakpointsAfterLiveEdit: function(uiSourceCode, oldSource, newSource)
204 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
206 // Clear and re-create breakpoints according to text diff.
207 var diff = Array.diff(oldSource.split("\n"), newSource.split("\n"));
208 for (var lineNumber in breakpoints) {
209 var breakpoint = breakpoints[lineNumber];
211 this.removeBreakpoint(uiSourceCode, lineNumber);
213 var newLineNumber = diff.left[lineNumber].row;
214 if (newLineNumber === undefined) {
215 for (var i = lineNumber - 1; i >= 0; --i) {
216 if (diff.left[i].row === undefined)
218 var shiftedLineNumber = diff.left[i].row + lineNumber - i;
219 if (shiftedLineNumber < diff.right.length) {
220 var originalLineNumber = diff.right[shiftedLineNumber].row;
221 if (originalLineNumber === lineNumber || originalLineNumber === undefined)
222 newLineNumber = shiftedLineNumber;
227 if (newLineNumber !== undefined)
228 this.setBreakpoint(uiSourceCode, newLineNumber, breakpoint.condition, breakpoint.enabled);
232 setFormatSource: function(formatSource)
234 if (this._formatSource === formatSource)
237 this._formatSource = formatSource;
238 this._breakpointManager.reset();
239 for (var id in this._rawSourceCode)
240 this._rawSourceCode[id].setFormatted(this._formatSource);
242 if (WebInspector.debuggerModel.callFrames)
243 this._debuggerPaused();
246 _consoleMessageAdded: function(event)
248 var message = event.data;
249 if (!message.url || !message.isErrorOrWarning() || !message.message)
252 var rawSourceCode = this._rawSourceCodeForScript(message.url);
256 rawSourceCode.messages.push(message);
257 if (rawSourceCode.uiSourceCode) {
258 message._presentationMessage = this._createPresentationMessage(message, rawSourceCode.uiSourceCode);
259 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, message._presentationMessage);
263 _createPresentationMessage: function(message, uiSourceCode)
265 // FIXME(62725): stack trace line/column numbers are one-based.
266 var lineNumber = message.stackTrace ? message.stackTrace[0].lineNumber - 1 : message.line - 1;
267 var columnNumber = message.stackTrace ? message.stackTrace[0].columnNumber - 1 : 0;
268 var uiLocation = uiSourceCode.rawSourceCode.rawLocationToUILocation({ lineNumber: lineNumber, columnNumber: columnNumber });
269 var presentationMessage = {};
270 presentationMessage.uiSourceCode = uiLocation.uiSourceCode;
271 presentationMessage.lineNumber = uiLocation.lineNumber;
272 presentationMessage.originalMessage = message;
273 return presentationMessage;
276 _consoleCleared: function()
278 for (var id in this._rawSourceCode)
279 this._rawSourceCode[id].messages = [];
280 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessagesCleared);
283 continueToLine: function(uiSourceCode, lineNumber)
285 var rawLocation = uiSourceCode.rawSourceCode.uiLocationToRawLocation(lineNumber, 0);
286 WebInspector.debuggerModel.continueToLocation(rawLocation);
289 breakpointsForUISourceCode: function(uiSourceCode)
291 var breakpointsMap = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
292 var breakpointsList = [];
293 for (var lineNumber in breakpointsMap)
294 breakpointsList.push(breakpointsMap[lineNumber]);
295 return breakpointsList;
298 messagesForUISourceCode: function(uiSourceCode)
300 var rawSourceCode = uiSourceCode.rawSourceCode;
302 for (var i = 0; i < rawSourceCode.messages.length; ++i)
303 messages.push(rawSourceCode.messages[i]._presentationMessage);
307 setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
309 this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
312 setBreakpointEnabled: function(uiSourceCode, lineNumber, enabled)
314 var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber);
317 this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
318 this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, breakpoint.condition, enabled);
321 updateBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
323 this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
324 this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
327 removeBreakpoint: function(uiSourceCode, lineNumber)
329 this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
332 findBreakpoint: function(uiSourceCode, lineNumber)
334 return this._breakpointManager.breakpointsForUISourceCode(uiSourceCode)[lineNumber];
337 _breakpointAdded: function(breakpoint)
339 if (breakpoint.uiSourceCode)
340 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, breakpoint);
343 _breakpointRemoved: function(breakpoint)
345 if (breakpoint.uiSourceCode)
346 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, breakpoint);
349 _debuggerPaused: function()
351 var callFrames = WebInspector.debuggerModel.callFrames;
352 this._presentationCallFrames = [];
353 for (var i = 0; i < callFrames.length; ++i) {
354 var callFrame = callFrames[i];
356 var script = WebInspector.debuggerModel.scriptForSourceID(callFrame.location.scriptId);
358 rawSourceCode = this._rawSourceCodeForScript(script.sourceURL, script.scriptId);
359 this._presentationCallFrames.push(new WebInspector.PresentationCallFrame(callFrame, i, this, rawSourceCode));
361 var details = WebInspector.debuggerModel.debuggerPausedDetails;
362 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, { callFrames: this._presentationCallFrames, details: details });
364 this.selectedCallFrame = this._presentationCallFrames[this._selectedCallFrameIndex];
367 _debuggerResumed: function()
369 this._presentationCallFrames = [];
370 this._selectedCallFrameIndex = 0;
371 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed);
374 set selectedCallFrame(callFrame)
376 this._selectedCallFrameIndex = callFrame.index;
378 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, callFrame);
381 get selectedCallFrame()
383 return this._presentationCallFrames[this._selectedCallFrameIndex];
386 _rawSourceCodeForScript: function(sourceURL, scriptId)
389 var script = WebInspector.debuggerModel.scriptForSourceID(scriptId);
392 sourceURL = script.sourceURL;
394 return this._rawSourceCode[this._createRawSourceCodeId(sourceURL, scriptId)];
397 _scriptForRawSourceCode: function(rawSourceCode)
399 function filter(script)
401 return this._createRawSourceCodeId(script.sourceURL, script.scriptId) === rawSourceCode.id;
403 return WebInspector.debuggerModel.queryScripts(filter.bind(this))[0];
406 _createRawSourceCodeId: function(sourceURL, scriptId)
408 return sourceURL || scriptId;
411 _debuggerReset: function()
413 this._rawSourceCode = {};
414 this._presentationCallFrames = [];
415 this._selectedCallFrameIndex = 0;
416 this._breakpointManager.debuggerReset();
420 WebInspector.DebuggerPresentationModel.prototype.__proto__ = WebInspector.Object.prototype;
425 WebInspector.PresentationCallFrame = function(callFrame, index, model, rawSourceCode)
427 this._callFrame = callFrame;
430 this._rawSourceCode = rawSourceCode;
431 this._script = WebInspector.debuggerModel.scriptForSourceID(callFrame.location.scriptId);
434 WebInspector.PresentationCallFrame.prototype = {
437 return this._callFrame.functionName;
442 return this._callFrame.type;
445 get isInternalScript()
447 return !this._script;
452 if (this._rawSourceCode && this._rawSourceCode.uiSourceCode)
453 return this._rawSourceCode.uiSourceCode.url;
458 return this._callFrame.scopeChain;
463 return this._callFrame.this;
473 if (this._rawSourceCode)
474 this._rawSourceCode.forceUpdateSourceMapping();
477 evaluate: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback)
479 function didEvaluateOnCallFrame(error, result, wasThrown)
482 console.error(error);
487 if (returnByValue && !wasThrown)
488 callback(result, wasThrown);
490 callback(WebInspector.RemoteObject.fromPayload(result), wasThrown);
492 DebuggerAgent.evaluateOnCallFrame(this._callFrame.id, code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluateOnCallFrame.bind(this));
495 sourceLine: function(callback)
497 var rawLocation = this._callFrame.location;
498 if (!this._rawSourceCode) {
499 callback(undefined, rawLocation.lineNumber);
503 if (this._rawSourceCode.uiSourceCode) {
504 var uiLocation = this._rawSourceCode.rawLocationToUILocation(rawLocation);
505 callback(uiLocation.uiSourceCode, uiLocation.lineNumber);
509 function sourceMappingUpdated()
511 this._rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingUpdated, this);
512 var uiLocation = this._rawSourceCode.rawLocationToUILocation(rawLocation);
513 callback(uiLocation.uiSourceCode, uiLocation.lineNumber);
515 this._rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingUpdated, this);
521 * @extends {WebInspector.ResourceDomainModelBinding}
523 WebInspector.DebuggerPresentationModelResourceBinding = function(model)
525 this._presentationModel = model;
526 WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Script, this);
529 WebInspector.DebuggerPresentationModelResourceBinding.prototype = {
530 canSetContent: function(resource)
532 var rawSourceCode = this._presentationModel._rawSourceCodeForScript(resource.url)
535 return this._presentationModel.canEditScriptSource(rawSourceCode.uiSourceCode);
538 setContent: function(resource, content, majorChange, userCallback)
543 var rawSourceCode = this._presentationModel._rawSourceCodeForScript(resource.url);
544 if (!rawSourceCode) {
545 userCallback("Resource is not editable");
549 resource.requestContent(this._setContentWithInitialContent.bind(this, rawSourceCode.uiSourceCode, content, userCallback));
552 _setContentWithInitialContent: function(uiSourceCode, content, userCallback, oldContent)
554 function callback(error)
559 this._presentationModel._updateBreakpointsAfterLiveEdit(uiSourceCode, oldContent, content);
561 this._presentationModel.setScriptSource(uiSourceCode, content, callback.bind(this));
565 WebInspector.DebuggerPresentationModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype;
568 * @type {?WebInspector.DebuggerPresentationModel}
570 WebInspector.debuggerPresentationModel = null;