Web Inspector: Execution line in selected call frame should keep showing after toggli...
[WebKit-https.git] / Source / WebCore / inspector / front-end / DebuggerPresentationModel.js
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
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
13  * distribution.
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.
17  *
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.
29  */
30
31 /**
32  * @constructor
33  * @extends {WebInspector.Object}
34  */
35 WebInspector.DebuggerPresentationModel = function()
36 {
37     // FIXME: apply formatter from outside as a generic mapping.
38     this._formatter = new WebInspector.ScriptFormatter();
39     this._rawSourceCode = {};
40     this._presentationCallFrames = [];
41
42     this._breakpointManager = new WebInspector.BreakpointManager(WebInspector.settings.breakpoints, this._breakpointAdded.bind(this), this._breakpointRemoved.bind(this), WebInspector.debuggerModel);
43
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);
49
50     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
51     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
52
53     new WebInspector.DebuggerPresentationModelResourceBinding(this);
54 }
55
56 WebInspector.DebuggerPresentationModel.Events = {
57     UISourceCodeAdded: "source-file-added",
58     UISourceCodeReplaced: "source-file-replaced",
59     UISourceCodeRemoved: "source-file-removed",
60     ConsoleMessageAdded: "console-message-added",
61     ConsoleMessagesCleared: "console-messages-cleared",
62     BreakpointAdded: "breakpoint-added",
63     BreakpointRemoved: "breakpoint-removed",
64     DebuggerPaused: "debugger-paused",
65     DebuggerResumed: "debugger-resumed",
66     CallFrameSelected: "call-frame-selected",
67     ConsoleCommandEvaluatedInSelectedCallFrame: "console-command-evaluated-in-selected-call-frame",
68     ExecutionLineChanged: "execution-line-changed"
69 }
70
71 WebInspector.DebuggerPresentationModel.prototype = {
72     /**
73      * @param {WebInspector.DebuggerPresentationModel.LinkifierFormatter=} formatter
74      */
75     createLinkifier: function(formatter)
76     {
77         return new WebInspector.DebuggerPresentationModel.Linkifier(this, formatter);
78     },
79
80     /**
81      * @param {WebInspector.PresentationCallFrame} callFrame
82      * @return {WebInspector.Placard}
83      */
84     createPlacard: function(callFrame)
85     {
86         var title = callFrame._callFrame.functionName || WebInspector.UIString("(anonymous function)");
87         var placard = new WebInspector.Placard(title, "");
88
89         var rawSourceCode = callFrame._rawSourceCode;
90         function updatePlacard()
91         {
92             var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(callFrame._callFrame.location);
93             placard.subtitle = WebInspector.displayNameForURL(uiLocation.uiSourceCode.url) + ":" + (uiLocation.lineNumber + 1);
94             placard._text = WebInspector.UIString("%s() at %s", placard.title, placard.subtitle);
95         }
96         if (rawSourceCode.sourceMapping)
97             updatePlacard.call(this);
98         rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, updatePlacard, this);
99         return placard;
100     },
101
102     /**
103      * @param {WebInspector.Event} event
104      */
105     _parsedScriptSource: function(event)
106     {
107         var script = /** @type {WebInspector.Script} */ event.data;
108         this._addScript(script);
109     },
110
111     /**
112      * @param {WebInspector.Event} event
113      */
114     _failedToParseScriptSource: function(event)
115     {
116         var script = /** @type {WebInspector.Script} */ event.data;
117         this._addScript(script);
118     },
119
120     /**
121      * @param {WebInspector.Script} script
122      */
123     _addScript: function(script)
124     {
125         var rawSourceCodeId = this._createRawSourceCodeId(script);
126         var rawSourceCode = this._rawSourceCode[rawSourceCodeId];
127         if (rawSourceCode) {
128             rawSourceCode.addScript(script);
129             return;
130         }
131
132         var resource;
133         if (script.sourceURL)
134             resource = WebInspector.networkManager.inflightResourceForURL(script.sourceURL) || WebInspector.resourceForURL(script.sourceURL);
135         rawSourceCode = new WebInspector.RawSourceCode(rawSourceCodeId, script, resource, this._formatter, this._formatSource);
136         this._rawSourceCode[rawSourceCodeId] = rawSourceCode;
137         if (rawSourceCode.sourceMapping)
138             this._updateSourceMapping(rawSourceCode, null);
139         rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._sourceMappingUpdated, this);
140     },
141
142     /**
143      * @param {WebInspector.Event} event
144      */
145     _sourceMappingUpdated: function(event)
146     {
147         var rawSourceCode = /** @type {WebInspector.RawSourceCode} */ event.target;
148         var oldSourceMapping = /** @type {WebInspector.RawSourceCode.SourceMapping} */ event.data["oldSourceMapping"];
149         this._updateSourceMapping(rawSourceCode, oldSourceMapping);
150     },
151
152     /**
153      * @return {Array.<WebInspector.UISourceCode>}
154      */
155     uiSourceCodes: function()
156     {
157         var result = [];
158         for (var id in this._rawSourceCode) {
159             var uiSourceCodeList = this._rawSourceCode[id].sourceMapping.uiSourceCodeList();
160             for (var i = 0; i < uiSourceCodeList.length; ++i)
161                 result.push(uiSourceCodeList[i]);
162         }
163         return result;
164     },
165
166     /**
167      * @param {WebInspector.RawSourceCode} rawSourceCode
168      * @param {WebInspector.RawSourceCode.SourceMapping} oldSourceMapping
169      */
170     _updateSourceMapping: function(rawSourceCode, oldSourceMapping)
171     {
172         if (oldSourceMapping) {
173             var oldUISourceCodeList = oldSourceMapping.uiSourceCodeList();
174             for (var i = 0; i < oldUISourceCodeList.length; ++i) {
175                 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(oldUISourceCodeList[i]);
176                 for (var lineNumber in breakpoints) {
177                     var breakpoint = breakpoints[lineNumber];
178                     this._breakpointRemoved(breakpoint);
179                     delete breakpoint.uiSourceCode;
180                 }
181             }
182         }
183
184         this._restoreBreakpoints(rawSourceCode);
185         this._restoreConsoleMessages(rawSourceCode);
186
187         if (!oldSourceMapping) {
188             var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList();
189             for (var i = 0; i < uiSourceCodeList.length; ++i)
190                 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeAdded, uiSourceCodeList[i]);
191         } else {
192             var eventData = { uiSourceCodeList: rawSourceCode.sourceMapping.uiSourceCodeList(), oldUISourceCodeList: oldSourceMapping.uiSourceCodeList() };
193             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeReplaced, eventData);
194         }
195     },
196
197     /**
198      * @param {WebInspector.RawSourceCode} rawSourceCode
199      */
200     _restoreBreakpoints: function(rawSourceCode)
201     {
202         var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList();
203         for (var i = 0; i < uiSourceCodeList.length; ++i) {
204             var uiSourceCode = uiSourceCodeList[i];
205             this._breakpointManager.uiSourceCodeAdded(uiSourceCode);
206             var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
207             for (var lineNumber in breakpoints)
208                 this._breakpointAdded(breakpoints[lineNumber]);
209         }
210
211     },
212
213     /**
214      * @param {WebInspector.RawSourceCode} rawSourceCode
215      */
216     _restoreConsoleMessages: function(rawSourceCode)
217     {
218         var messages = rawSourceCode.messages;
219         for (var i = 0; i < messages.length; ++i)
220             messages[i]._presentationMessage = this._createPresentationMessage(messages[i], rawSourceCode.sourceMapping);
221     },
222
223     /**
224      * @param {WebInspector.UISourceCode} uiSourceCode
225      * @return {boolean}
226      */
227     canEditScriptSource: function(uiSourceCode)
228     {
229         if (!Preferences.canEditScriptSource || this._formatSource)
230             return false;
231         var rawSourceCode = uiSourceCode.rawSourceCode;
232         var script = this._scriptForRawSourceCode(rawSourceCode);
233         return script && !script.lineOffset && !script.columnOffset;
234     },
235
236     /**
237      * @param {WebInspector.UISourceCode} uiSourceCode
238      * @param {string} newSource
239      * @param {function(?Protocol.Error)} callback
240      */
241     setScriptSource: function(uiSourceCode, newSource, callback)
242     {
243         var rawSourceCode = uiSourceCode.rawSourceCode;
244         var script = this._scriptForRawSourceCode(rawSourceCode);
245
246         /**
247          * @this {WebInspector.DebuggerPresentationModel}
248          * @param {?Protocol.Error} error
249          */
250         function didEditScriptSource(error)
251         {
252             callback(error);
253             if (error)
254                 return;
255
256             var resource = WebInspector.resourceForURL(rawSourceCode.url);
257             if (resource)
258                 resource.addRevision(newSource);
259
260             uiSourceCode.contentChanged(newSource);
261
262             if (WebInspector.debuggerModel.callFrames)
263                 this._debuggerPaused();
264         }
265         WebInspector.debuggerModel.setScriptSource(script.scriptId, newSource, didEditScriptSource.bind(this));
266     },
267
268     /**
269      * @param {WebInspector.UISourceCode} uiSourceCode
270      * @param {string} oldSource
271      * @param {string} newSource
272      */
273     _updateBreakpointsAfterLiveEdit: function(uiSourceCode, oldSource, newSource)
274     {
275         var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
276
277         // Clear and re-create breakpoints according to text diff.
278         var diff = Array.diff(oldSource.split("\n"), newSource.split("\n"));
279         for (var lineNumber in breakpoints) {
280             var breakpoint = breakpoints[lineNumber];
281
282             this.removeBreakpoint(uiSourceCode, parseInt(lineNumber, 10));
283
284             var newLineNumber = diff.left[lineNumber].row;
285             if (newLineNumber === undefined) {
286                 for (var i = lineNumber - 1; i >= 0; --i) {
287                     if (diff.left[i].row === undefined)
288                         continue;
289                     var shiftedLineNumber = diff.left[i].row + lineNumber - i;
290                     if (shiftedLineNumber < diff.right.length) {
291                         var originalLineNumber = diff.right[shiftedLineNumber].row;
292                         if (originalLineNumber === lineNumber || originalLineNumber === undefined)
293                             newLineNumber = shiftedLineNumber;
294                     }
295                     break;
296                 }
297             }
298             if (newLineNumber !== undefined)
299                 this.setBreakpoint(uiSourceCode, newLineNumber, breakpoint.condition, breakpoint.enabled);
300         }
301     },
302
303     /**
304      * @param {boolean} formatSource
305      */
306     setFormatSource: function(formatSource)
307     {
308         if (this._formatSource === formatSource)
309             return;
310
311         this._formatSource = formatSource;
312         this._breakpointManager.reset();
313         for (var id in this._rawSourceCode)
314             this._rawSourceCode[id].setFormatted(this._formatSource);
315     },
316
317     /**
318      * @param {WebInspector.Event} event
319      */
320     _consoleMessageAdded: function(event)
321     {
322         var message = /** @type {WebInspector.ConsoleMessage} */ event.data;
323         if (!message.url || !message.isErrorOrWarning() || !message.message)
324             return;
325
326         var rawSourceCode = this._rawSourceCodeForScriptWithURL(message.url);
327         if (!rawSourceCode)
328             return;
329
330         rawSourceCode.messages.push(message);
331         if (rawSourceCode.sourceMapping) {
332             message._presentationMessage = this._createPresentationMessage(message, rawSourceCode.sourceMapping);
333             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, message._presentationMessage);
334         }
335     },
336
337     /**
338      * @param {WebInspector.ConsoleMessage} message
339      * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping
340      * @return {WebInspector.PresentationConsoleMessage}
341      */
342     _createPresentationMessage: function(message, sourceMapping)
343     {
344         // FIXME(62725): stack trace line/column numbers are one-based.
345         var lineNumber = message.stackTrace ? message.stackTrace[0].lineNumber - 1 : message.line - 1;
346         var columnNumber = message.stackTrace ? message.stackTrace[0].columnNumber - 1 : 0;
347         var uiLocation = sourceMapping.rawLocationToUILocation(/** @type {DebuggerAgent.Location} */ { lineNumber: lineNumber, columnNumber: columnNumber });
348         var presentationMessage = new WebInspector.PresentationConsoleMessage(uiLocation.uiSourceCode, uiLocation.lineNumber, message);
349         return presentationMessage;
350     },
351
352     _consoleCleared: function()
353     {
354         for (var id in this._rawSourceCode)
355             this._rawSourceCode[id].messages = [];
356         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessagesCleared);
357     },
358
359     /**
360      * @param {WebInspector.UISourceCode} uiSourceCode
361      * @param {number} lineNumber
362      */
363     continueToLine: function(uiSourceCode, lineNumber)
364     {
365         // FIXME: use RawSourceCode.uiLocationToRawLocation.
366         var rawLocation = uiSourceCode.rawSourceCode.sourceMapping.uiLocationToRawLocation(uiSourceCode, lineNumber, 0);
367         WebInspector.debuggerModel.continueToLocation(rawLocation);
368     },
369
370     /**
371      * @param {WebInspector.UISourceCode} uiSourceCode
372      * @return {Array.<WebInspector.Breakpoint>}
373      */
374     breakpointsForUISourceCode: function(uiSourceCode)
375     {
376         var breakpointsMap = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
377         var breakpointsList = [];
378         for (var lineNumber in breakpointsMap)
379             breakpointsList.push(breakpointsMap[lineNumber]);
380         return breakpointsList;
381     },
382
383     /**
384      * @param {WebInspector.UISourceCode} uiSourceCode
385      * @return {Array.<WebInspector.ConsoleMessage>}
386      */
387     messagesForUISourceCode: function(uiSourceCode)
388     {
389         var rawSourceCode = uiSourceCode.rawSourceCode;
390         var messages = [];
391         for (var i = 0; i < rawSourceCode.messages.length; ++i)
392             messages.push(rawSourceCode.messages[i]._presentationMessage);
393         return messages;
394     },
395
396     /**
397      * @param {WebInspector.UISourceCode} uiSourceCode
398      * @param {number} lineNumber
399      * @param {string} condition
400      * @param {boolean} enabled
401      */
402     setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
403     {
404         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
405     },
406
407     /**
408      * @param {WebInspector.UISourceCode} uiSourceCode
409      * @param {number} lineNumber
410      * @param {boolean} enabled
411      */
412     setBreakpointEnabled: function(uiSourceCode, lineNumber, enabled)
413     {
414         var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber);
415         if (!breakpoint)
416             return;
417         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
418         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, breakpoint.condition, enabled);
419     },
420
421     /**
422      * @param {WebInspector.UISourceCode} uiSourceCode
423      * @param {number} lineNumber
424      * @param {string} condition
425      * @param {boolean} enabled
426      */
427     updateBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
428     {
429         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
430         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
431     },
432
433     /**
434      * @param {WebInspector.UISourceCode} uiSourceCode
435      * @param {number} lineNumber
436      */
437     removeBreakpoint: function(uiSourceCode, lineNumber)
438     {
439         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
440     },
441
442     /**
443      * @param {WebInspector.UISourceCode} uiSourceCode
444      * @param {number} lineNumber
445      * @return {WebInspector.Breakpoint|undefined}
446      */
447     findBreakpoint: function(uiSourceCode, lineNumber)
448     {
449         return this._breakpointManager.breakpointsForUISourceCode(uiSourceCode)[lineNumber];
450     },
451
452     /**
453      * @param {WebInspector.Breakpoint} breakpoint
454      */
455     _breakpointAdded: function(breakpoint)
456     {
457         if (breakpoint.uiSourceCode)
458             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, breakpoint);
459     },
460
461     /**
462      * @param {WebInspector.Breakpoint} breakpoint
463      */
464     _breakpointRemoved: function(breakpoint)
465     {
466         if (breakpoint.uiSourceCode)
467             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, breakpoint);
468     },
469
470     _debuggerPaused: function()
471     {
472         var callFrames = WebInspector.debuggerModel.callFrames;
473         this._presentationCallFrames = [];
474         for (var i = 0; i < callFrames.length; ++i) {
475             var callFrame = callFrames[i];
476             var script = WebInspector.debuggerModel.scriptForSourceID(callFrame.location.scriptId);
477             if (!script)
478                 continue;
479             var rawSourceCode = this._rawSourceCodeForScript(script);
480             this._presentationCallFrames.push(new WebInspector.PresentationCallFrame(callFrame, i, this, rawSourceCode));
481         }
482         var details = WebInspector.debuggerModel.debuggerPausedDetails;
483         this.selectedCallFrame = this._presentationCallFrames[0];
484         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, { callFrames: this._presentationCallFrames, details: details });
485     },
486
487     _debuggerResumed: function()
488     {
489         this._presentationCallFrames = [];
490         this.selectedCallFrame = null;
491         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed);
492     },
493
494     get paused()
495     {
496         return !!WebInspector.debuggerModel.debuggerPausedDetails;
497     },
498
499     set selectedCallFrame(callFrame)
500     {
501         if (this._selectedCallFrame)
502             this._selectedCallFrame.rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this);
503         this._selectedCallFrame = callFrame;
504         if (!this._selectedCallFrame)
505             return;
506
507         this._selectedCallFrame.rawSourceCode.forceUpdateSourceMapping();
508         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, callFrame);
509
510         this._selectedCallFrame.rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this);
511     },
512
513     get selectedCallFrame()
514     {
515         return this._selectedCallFrame;
516     },
517
518     /**
519      * @param {function(?WebInspector.RemoteObject, boolean, RuntimeAgent.RemoteObject=)} callback
520      */
521     evaluateInSelectedCallFrame: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback)
522     {
523         /**
524          * @param {?RuntimeAgent.RemoteObject} result
525          * @param {boolean} wasThrown
526          */
527         function didEvaluate(result, wasThrown)
528         {
529             if (returnByValue)
530                 callback(null, wasThrown, wasThrown ? null : result);
531             else
532                 callback(WebInspector.RemoteObject.fromPayload(result), wasThrown);
533             
534             if (objectGroup === "console")
535                 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleCommandEvaluatedInSelectedCallFrame);
536         }
537         
538         this.selectedCallFrame.evaluate(code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluate.bind(this));
539     },
540
541     /**
542      * @param {function(Object)} callback
543      */
544     getSelectedCallFrameVariables: function(callback)
545     {
546         var result = { this: true };
547
548         var selectedCallFrame = this.selectedCallFrame;
549         if (!selectedCallFrame)
550             callback(result);
551
552         var pendingRequests = 0;
553
554         function propertiesCollected(properties)
555         {
556             for (var i = 0; properties && i < properties.length; ++i)
557                 result[properties[i].name] = true;
558             if (--pendingRequests == 0)
559                 callback(result);
560         }
561
562         for (var i = 0; i < selectedCallFrame.scopeChain.length; ++i) {
563             var scope = selectedCallFrame.scopeChain[i];
564             var object = WebInspector.RemoteObject.fromPayload(scope.object);
565             pendingRequests++;
566             object.getAllProperties(propertiesCollected);
567         }
568     },
569
570     /**
571      * @param {WebInspector.Event} event
572      */
573     _dispatchExecutionLineChanged: function(event)
574     {
575         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ExecutionLineChanged, this.executionLineLocation);
576     },
577
578     /**
579      * @type {WebInspector.UILocation}
580      */
581     get executionLineLocation()
582     {
583         if (!this._selectedCallFrame.rawSourceCode.sourceMapping)
584             return;
585         
586         var rawLocation = this._selectedCallFrame._callFrame.location;
587         var uiLocation = this._selectedCallFrame.rawSourceCode.sourceMapping.rawLocationToUILocation(rawLocation);
588         return uiLocation;
589     },
590
591     /**
592      * @param {string} sourceURL
593      */
594     _rawSourceCodeForScriptWithURL: function(sourceURL)
595     {
596         return this._rawSourceCode[sourceURL];
597     },
598
599     /**
600      * @param {WebInspector.Script} script
601      */
602     _rawSourceCodeForScript: function(script)
603     {
604         return this._rawSourceCode[this._createRawSourceCodeId(script)];
605     },
606
607     /**
608      * @param {WebInspector.RawSourceCode} rawSourceCode
609      */
610     _scriptForRawSourceCode: function(rawSourceCode)
611     {
612         /**
613          * @this {WebInspector.DebuggerPresentationModel}
614          * @param {WebInspector.Script} script
615          * @return {boolean}
616          */
617         function filter(script)
618         {
619             return this._createRawSourceCodeId(script) === rawSourceCode.id;
620         }
621         return WebInspector.debuggerModel.queryScripts(filter.bind(this))[0];
622     },
623
624     /**
625      * @param {WebInspector.Script} script
626      */
627     _createRawSourceCodeId: function(script)
628     {
629         return script.sourceURL || script.scriptId;
630     },
631
632     _debuggerReset: function()
633     {
634         for (var id in this._rawSourceCode) {
635             var rawSourceCode = this._rawSourceCode[id];
636             if (rawSourceCode.sourceMapping) {
637                 var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList();
638                 for (var i = 0; i < uiSourceCodeList.length; ++i)
639                     this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeRemoved, uiSourceCodeList[i]);
640             }
641             rawSourceCode.removeAllListeners();
642         }
643         this._rawSourceCode = {};
644         this._presentationCallFrames = [];
645         this._selectedCallFrame = null;
646         this._breakpointManager.debuggerReset();
647     }
648 }
649
650 WebInspector.DebuggerPresentationModel.prototype.__proto__ = WebInspector.Object.prototype;
651
652 /**
653  * @constructor
654  * @param {WebInspector.UISourceCode} uiSourceCode
655  * @param {number} lineNumber
656  * @param {WebInspector.ConsoleMessage} originalMessage
657  */
658 WebInspector.PresentationConsoleMessage = function(uiSourceCode, lineNumber, originalMessage)
659 {
660     this.uiSourceCode = uiSourceCode;
661     this.lineNumber = lineNumber;
662     this.originalMessage = originalMessage;
663 }
664
665 /**
666  * @constructor
667  * @param {DebuggerAgent.CallFrame} callFrame
668  * @param {number} index
669  * @param {WebInspector.DebuggerPresentationModel} model
670  * @param {WebInspector.RawSourceCode} rawSourceCode
671  */
672 WebInspector.PresentationCallFrame = function(callFrame, index, model, rawSourceCode)
673 {
674     this._callFrame = callFrame;
675     this._index = index;
676     this._model = model;
677     this._rawSourceCode = rawSourceCode;
678 }
679
680 WebInspector.PresentationCallFrame.prototype = {
681     /**
682      * @return {string}
683      */
684     get type()
685     {
686         return this._callFrame.type;
687     },
688
689     /**
690      * @return {Array.<DebuggerAgent.Scope>}
691      */
692     get scopeChain()
693     {
694         return this._callFrame.scopeChain;
695     },
696
697     /**
698      * @return {RuntimeAgent.RemoteObject}
699      */
700     get this()
701     {
702         return this._callFrame.this;
703     },
704
705     /**
706      * @return {number}
707      */
708     get index()
709     {
710         return this._index;
711     },
712
713     /**
714      * @return {WebInspector.RawSourceCode}
715      */
716     get rawSourceCode()
717     {
718         return this._rawSourceCode;
719     },
720
721     /**
722      * @param {string} code
723      * @param {string} objectGroup
724      * @param {boolean} includeCommandLineAPI
725      * @param {boolean} returnByValue
726      * @param {function(?RuntimeAgent.RemoteObject, boolean)=} callback
727      */
728     evaluate: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback)
729     {
730         /**
731          * @this {WebInspector.PresentationCallFrame}
732          * @param {?Protocol.Error} error
733          * @param {RuntimeAgent.RemoteObject} result
734          * @param {boolean} wasThrown
735          */
736         function didEvaluateOnCallFrame(error, result, wasThrown)
737         {
738             if (error) {
739                 console.error(error);
740                 callback(null, false);
741                 return;
742             }
743             callback(result, wasThrown);
744         }
745         DebuggerAgent.evaluateOnCallFrame(this._callFrame.callFrameId, code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluateOnCallFrame.bind(this));
746     },
747
748     /**
749      * @param {function(WebInspector.UILocation)} callback
750      */
751     uiLocation: function(callback)
752     {
753         function sourceMappingReady()
754         {
755             this._rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this);
756             callback(this._rawSourceCode.sourceMapping.rawLocationToUILocation(this._callFrame.location));
757         }
758         if (this._rawSourceCode.sourceMapping)
759             sourceMappingReady.call(this);
760         else
761             this._rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this);
762     }
763 }
764
765 /**
766  * @constructor
767  * @implements {WebInspector.ResourceDomainModelBinding}
768  * @param {WebInspector.DebuggerPresentationModel} model
769  */
770 WebInspector.DebuggerPresentationModelResourceBinding = function(model)
771 {
772     this._presentationModel = model;
773     WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Script, this);
774 }
775
776 WebInspector.DebuggerPresentationModelResourceBinding.prototype = {
777     /**
778      * @param {WebInspector.Resource} resource
779      */
780     canSetContent: function(resource)
781     {
782         var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url)
783         if (!rawSourceCode)
784             return false;
785         return this._presentationModel.canEditScriptSource(rawSourceCode.sourceMapping.uiSourceCodeList()[0]);
786     },
787
788     /**
789      * @param {WebInspector.Resource} resource
790      * @param {string} content
791      * @param {boolean} majorChange
792      * @param {function(?Protocol.Error)} userCallback
793      */
794     setContent: function(resource, content, majorChange, userCallback)
795     {
796         if (!majorChange)
797             return;
798
799         var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url);
800         if (!rawSourceCode) {
801             userCallback("Resource is not editable");
802             return;
803         }
804
805         resource.requestContent(this._setContentWithInitialContent.bind(this, rawSourceCode.sourceMapping.uiSourceCodeList()[0], content, userCallback));
806     },
807
808     /**
809      * @param {WebInspector.UISourceCode} uiSourceCode
810      * @param {string} content
811      * @param {function(?Protocol.Error)} userCallback
812      * @param {string} oldContent
813      */
814     _setContentWithInitialContent: function(uiSourceCode, content, userCallback, oldContent)
815     {
816         /**
817          * @this {WebInspector.DebuggerPresentationModelResourceBinding}
818          * @param {?Protocol.Error} error
819          */
820         function callback(error)
821         {
822             if (userCallback)
823                 userCallback(error);
824             if (!error)
825                 this._presentationModel._updateBreakpointsAfterLiveEdit(uiSourceCode, oldContent, content);
826         }
827         this._presentationModel.setScriptSource(uiSourceCode, content, callback.bind(this));
828     }
829 }
830
831 /**
832  * @interface
833  */
834 WebInspector.DebuggerPresentationModel.LinkifierFormatter = function()
835 {
836 }
837
838 WebInspector.DebuggerPresentationModel.LinkifierFormatter.prototype = {
839     /**
840      * @param {WebInspector.RawSourceCode} rawSourceCode
841      * @param {Element} anchor
842      */
843     formatRawSourceCodeAnchor: function(rawSourceCode, anchor) { },
844 }
845
846 /**
847  * @constructor
848  * @implements {WebInspector.DebuggerPresentationModel.LinkifierFormatter}
849  * @param {number=} maxLength
850  */
851 WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter = function(maxLength)
852 {
853     this._maxLength = maxLength;
854 }
855
856 WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter.prototype = {
857     /**
858      * @param {WebInspector.RawSourceCode} rawSourceCode
859      * @param {Element} anchor
860      */
861     formatRawSourceCodeAnchor: function(rawSourceCode, anchor)
862     {
863         var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(anchor.rawLocation);
864
865         anchor.textContent = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber);
866             
867         var text = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber);
868         if (this._maxLength)
869             text = text.trimMiddle(this._maxLength);
870         anchor.textContent = text;
871     }
872 }
873
874 WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter.prototype.__proto__ = WebInspector.DebuggerPresentationModel.LinkifierFormatter.prototype;
875
876 /**
877  * @constructor
878  * @param {WebInspector.DebuggerPresentationModel} model
879  * @param {WebInspector.DebuggerPresentationModel.LinkifierFormatter=} formatter
880  */
881 WebInspector.DebuggerPresentationModel.Linkifier = function(model, formatter)
882 {
883     this._model = model;
884     this._formatter = formatter || new WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter();
885     this._anchorsForRawSourceCode = {};
886 }
887
888 WebInspector.DebuggerPresentationModel.Linkifier.prototype = {
889     /**
890      * @param {string} sourceURL
891      * @param {number=} lineNumber
892      * @param {number=} columnNumber
893      * @param {string=} classes
894      */
895     linkifyLocation: function(sourceURL, lineNumber, columnNumber, classes)
896     {
897         var rawSourceCode = this._model._rawSourceCodeForScriptWithURL(sourceURL);
898         if (!rawSourceCode)
899             return this.linkifyResource(sourceURL, lineNumber, classes);
900         
901         return this.linkifyRawSourceCode(rawSourceCode, lineNumber, columnNumber, classes);
902     },
903
904     /**
905      * @param {string} sourceURL
906      * @param {number=} lineNumber
907      * @param {string=} classes
908      */
909     linkifyResource: function(sourceURL, lineNumber, classes)
910     {
911         var linkText = WebInspector.formatLinkText(sourceURL, lineNumber);
912         var anchor = WebInspector.linkifyURLAsNode(sourceURL, linkText, classes, false);
913         anchor.setAttribute("preferred_panel", "resources");
914         anchor.setAttribute("line_number", lineNumber);
915         return anchor;
916     },
917
918     /**
919      * @param {WebInspector.RawSourceCode} rawSourceCode
920      * @param {number=} lineNumber
921      * @param {number=} columnNumber
922      * @param {string=} classes
923      */
924     linkifyRawSourceCode: function(rawSourceCode, lineNumber, columnNumber, classes)
925     {
926         var anchor = WebInspector.linkifyURLAsNode(rawSourceCode.url, "", classes, false);
927         anchor.rawLocation = { lineNumber: lineNumber, columnNumber: columnNumber };
928
929         var anchors = this._anchorsForRawSourceCode[rawSourceCode.id];
930         if (!anchors) {
931             anchors = [];
932             this._anchorsForRawSourceCode[rawSourceCode.id] = anchors;
933             rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this);
934         }
935
936         if (rawSourceCode.sourceMapping)
937             this._updateAnchor(rawSourceCode, anchor);
938         anchors.push(anchor);
939         return anchor;
940     },
941
942     reset: function()
943     {
944         for (var id in this._anchorsForRawSourceCode) {
945             if (this._model._rawSourceCode[id]) // In case of navigation the list of rawSourceCodes is empty.
946                 this._model._rawSourceCode[id].removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this);
947         }
948         this._anchorsForRawSourceCode = {};
949     },
950
951     /**
952      * @param {WebInspector.Event} event
953      */
954     _updateSourceAnchors: function(event)
955     {
956         var rawSourceCode = /** @type {WebInspector.RawSourceCode} */ event.target;
957         var anchors = this._anchorsForRawSourceCode[rawSourceCode.id];
958         for (var i = 0; i < anchors.length; ++i)
959             this._updateAnchor(rawSourceCode, anchors[i]);
960     },
961
962     /**
963      * @param {WebInspector.RawSourceCode} rawSourceCode
964      * @param {Element} anchor
965      */
966     _updateAnchor: function(rawSourceCode, anchor)
967     {
968         var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(anchor.rawLocation);
969         anchor.setAttribute("preferred_panel", "scripts");
970         anchor.uiSourceCode = uiLocation.uiSourceCode;
971         anchor.lineNumber = uiLocation.lineNumber;
972         
973         this._formatter.formatRawSourceCodeAnchor(rawSourceCode, anchor);
974     }
975 }
976
977 WebInspector.DebuggerPresentationModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype;
978
979 /**
980  * @type {?WebInspector.DebuggerPresentationModel}
981  */
982 WebInspector.debuggerPresentationModel = null;