1ba535af0c8ab51998ceeef2f74f77b5270bc830
[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         if (WebInspector.debuggerModel.callFrames)
317             this._debuggerPaused();
318     },
319
320     /**
321      * @param {WebInspector.Event} event
322      */
323     _consoleMessageAdded: function(event)
324     {
325         var message = /** @type {WebInspector.ConsoleMessage} */ event.data;
326         if (!message.url || !message.isErrorOrWarning() || !message.message)
327             return;
328
329         var rawSourceCode = this._rawSourceCodeForScriptWithURL(message.url);
330         if (!rawSourceCode)
331             return;
332
333         rawSourceCode.messages.push(message);
334         if (rawSourceCode.sourceMapping) {
335             message._presentationMessage = this._createPresentationMessage(message, rawSourceCode.sourceMapping);
336             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, message._presentationMessage);
337         }
338     },
339
340     /**
341      * @param {WebInspector.ConsoleMessage} message
342      * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping
343      * @return {WebInspector.PresentationConsoleMessage}
344      */
345     _createPresentationMessage: function(message, sourceMapping)
346     {
347         // FIXME(62725): stack trace line/column numbers are one-based.
348         var lineNumber = message.stackTrace ? message.stackTrace[0].lineNumber - 1 : message.line - 1;
349         var columnNumber = message.stackTrace ? message.stackTrace[0].columnNumber - 1 : 0;
350         var uiLocation = sourceMapping.rawLocationToUILocation(/** @type {DebuggerAgent.Location} */ { lineNumber: lineNumber, columnNumber: columnNumber });
351         var presentationMessage = new WebInspector.PresentationConsoleMessage(uiLocation.uiSourceCode, uiLocation.lineNumber, message);
352         return presentationMessage;
353     },
354
355     _consoleCleared: function()
356     {
357         for (var id in this._rawSourceCode)
358             this._rawSourceCode[id].messages = [];
359         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessagesCleared);
360     },
361
362     /**
363      * @param {WebInspector.UISourceCode} uiSourceCode
364      * @param {number} lineNumber
365      */
366     continueToLine: function(uiSourceCode, lineNumber)
367     {
368         // FIXME: use RawSourceCode.uiLocationToRawLocation.
369         var rawLocation = uiSourceCode.rawSourceCode.sourceMapping.uiLocationToRawLocation(uiSourceCode, lineNumber, 0);
370         WebInspector.debuggerModel.continueToLocation(rawLocation);
371     },
372
373     /**
374      * @param {WebInspector.UISourceCode} uiSourceCode
375      * @return {Array.<WebInspector.Breakpoint>}
376      */
377     breakpointsForUISourceCode: function(uiSourceCode)
378     {
379         var breakpointsMap = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode);
380         var breakpointsList = [];
381         for (var lineNumber in breakpointsMap)
382             breakpointsList.push(breakpointsMap[lineNumber]);
383         return breakpointsList;
384     },
385
386     /**
387      * @param {WebInspector.UISourceCode} uiSourceCode
388      * @return {Array.<WebInspector.ConsoleMessage>}
389      */
390     messagesForUISourceCode: function(uiSourceCode)
391     {
392         var rawSourceCode = uiSourceCode.rawSourceCode;
393         var messages = [];
394         for (var i = 0; i < rawSourceCode.messages.length; ++i)
395             messages.push(rawSourceCode.messages[i]._presentationMessage);
396         return messages;
397     },
398
399     /**
400      * @param {WebInspector.UISourceCode} uiSourceCode
401      * @param {number} lineNumber
402      * @param {string} condition
403      * @param {boolean} enabled
404      */
405     setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
406     {
407         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
408     },
409
410     /**
411      * @param {WebInspector.UISourceCode} uiSourceCode
412      * @param {number} lineNumber
413      * @param {boolean} enabled
414      */
415     setBreakpointEnabled: function(uiSourceCode, lineNumber, enabled)
416     {
417         var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber);
418         if (!breakpoint)
419             return;
420         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
421         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, breakpoint.condition, enabled);
422     },
423
424     /**
425      * @param {WebInspector.UISourceCode} uiSourceCode
426      * @param {number} lineNumber
427      * @param {string} condition
428      * @param {boolean} enabled
429      */
430     updateBreakpoint: function(uiSourceCode, lineNumber, condition, enabled)
431     {
432         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
433         this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled);
434     },
435
436     /**
437      * @param {WebInspector.UISourceCode} uiSourceCode
438      * @param {number} lineNumber
439      */
440     removeBreakpoint: function(uiSourceCode, lineNumber)
441     {
442         this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber);
443     },
444
445     /**
446      * @param {WebInspector.UISourceCode} uiSourceCode
447      * @param {number} lineNumber
448      * @return {WebInspector.Breakpoint|undefined}
449      */
450     findBreakpoint: function(uiSourceCode, lineNumber)
451     {
452         return this._breakpointManager.breakpointsForUISourceCode(uiSourceCode)[lineNumber];
453     },
454
455     /**
456      * @param {WebInspector.Breakpoint} breakpoint
457      */
458     _breakpointAdded: function(breakpoint)
459     {
460         if (breakpoint.uiSourceCode)
461             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, breakpoint);
462     },
463
464     /**
465      * @param {WebInspector.Breakpoint} breakpoint
466      */
467     _breakpointRemoved: function(breakpoint)
468     {
469         if (breakpoint.uiSourceCode)
470             this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, breakpoint);
471     },
472
473     _debuggerPaused: function()
474     {
475         var callFrames = WebInspector.debuggerModel.callFrames;
476         this._presentationCallFrames = [];
477         for (var i = 0; i < callFrames.length; ++i) {
478             var callFrame = callFrames[i];
479             var script = WebInspector.debuggerModel.scriptForSourceID(callFrame.location.scriptId);
480             if (!script)
481                 continue;
482             var rawSourceCode = this._rawSourceCodeForScript(script);
483             this._presentationCallFrames.push(new WebInspector.PresentationCallFrame(callFrame, i, this, rawSourceCode));
484         }
485         var details = WebInspector.debuggerModel.debuggerPausedDetails;
486         this.selectedCallFrame = this._presentationCallFrames[0];
487         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, { callFrames: this._presentationCallFrames, details: details });
488     },
489
490     _debuggerResumed: function()
491     {
492         this._presentationCallFrames = [];
493         this.selectedCallFrame = null;
494         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed);
495     },
496
497     get paused()
498     {
499         return !!WebInspector.debuggerModel.debuggerPausedDetails;
500     },
501
502     set selectedCallFrame(callFrame)
503     {
504         if (this._selectedCallFrame)
505             this._selectedCallFrame.rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this);
506         this._selectedCallFrame = callFrame;
507         if (!this._selectedCallFrame)
508             return;
509
510         this._selectedCallFrame.rawSourceCode.forceUpdateSourceMapping();
511         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, callFrame);
512
513         this._selectedCallFrame.rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this);
514     },
515
516     get selectedCallFrame()
517     {
518         return this._selectedCallFrame;
519     },
520
521     /**
522      * @param {function(?WebInspector.RemoteObject, boolean, RuntimeAgent.RemoteObject=)} callback
523      */
524     evaluateInSelectedCallFrame: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback)
525     {
526         /**
527          * @param {?RuntimeAgent.RemoteObject} result
528          * @param {boolean} wasThrown
529          */
530         function didEvaluate(result, wasThrown)
531         {
532             if (returnByValue)
533                 callback(null, wasThrown, wasThrown ? null : result);
534             else
535                 callback(WebInspector.RemoteObject.fromPayload(result), wasThrown);
536             
537             if (objectGroup === "console")
538                 this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleCommandEvaluatedInSelectedCallFrame);
539         }
540         
541         this.selectedCallFrame.evaluate(code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluate.bind(this));
542     },
543
544     /**
545      * @param {function(Object)} callback
546      */
547     getSelectedCallFrameVariables: function(callback)
548     {
549         var result = { this: true };
550
551         var selectedCallFrame = this.selectedCallFrame;
552         if (!selectedCallFrame)
553             callback(result);
554
555         var pendingRequests = 0;
556
557         function propertiesCollected(properties)
558         {
559             for (var i = 0; properties && i < properties.length; ++i)
560                 result[properties[i].name] = true;
561             if (--pendingRequests == 0)
562                 callback(result);
563         }
564
565         for (var i = 0; i < selectedCallFrame.scopeChain.length; ++i) {
566             var scope = selectedCallFrame.scopeChain[i];
567             var object = WebInspector.RemoteObject.fromPayload(scope.object);
568             pendingRequests++;
569             object.getAllProperties(propertiesCollected);
570         }
571     },
572
573     /**
574      * @param {WebInspector.Event} event
575      */
576     _dispatchExecutionLineChanged: function(event)
577     {
578         this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ExecutionLineChanged, this.executionLineLocation);
579     },
580
581     /**
582      * @type {WebInspector.UILocation}
583      */
584     get executionLineLocation()
585     {
586         if (!this._selectedCallFrame.rawSourceCode.sourceMapping)
587             return;
588         
589         var rawLocation = this._selectedCallFrame._callFrame.location;
590         var uiLocation = this._selectedCallFrame.rawSourceCode.sourceMapping.rawLocationToUILocation(rawLocation);
591         return uiLocation;
592     },
593
594     /**
595      * @param {string} sourceURL
596      */
597     _rawSourceCodeForScriptWithURL: function(sourceURL)
598     {
599         return this._rawSourceCode[sourceURL];
600     },
601
602     /**
603      * @param {WebInspector.Script} script
604      */
605     _rawSourceCodeForScript: function(script)
606     {
607         return this._rawSourceCode[this._createRawSourceCodeId(script)];
608     },
609
610     /**
611      * @param {WebInspector.RawSourceCode} rawSourceCode
612      */
613     _scriptForRawSourceCode: function(rawSourceCode)
614     {
615         /**
616          * @this {WebInspector.DebuggerPresentationModel}
617          * @param {WebInspector.Script} script
618          * @return {boolean}
619          */
620         function filter(script)
621         {
622             return this._createRawSourceCodeId(script) === rawSourceCode.id;
623         }
624         return WebInspector.debuggerModel.queryScripts(filter.bind(this))[0];
625     },
626
627     /**
628      * @param {WebInspector.Script} script
629      */
630     _createRawSourceCodeId: function(script)
631     {
632         return script.sourceURL || script.scriptId;
633     },
634
635     _debuggerReset: function()
636     {
637         for (var id in this._rawSourceCode) {
638             var rawSourceCode = this._rawSourceCode[id];
639             if (rawSourceCode.sourceMapping) {
640                 var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList();
641                 for (var i = 0; i < uiSourceCodeList.length; ++i)
642                     this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeRemoved, uiSourceCodeList[i]);
643             }
644             rawSourceCode.removeAllListeners();
645         }
646         this._rawSourceCode = {};
647         this._presentationCallFrames = [];
648         this._selectedCallFrame = null;
649         this._breakpointManager.debuggerReset();
650     }
651 }
652
653 WebInspector.DebuggerPresentationModel.prototype.__proto__ = WebInspector.Object.prototype;
654
655 /**
656  * @constructor
657  * @param {WebInspector.UISourceCode} uiSourceCode
658  * @param {number} lineNumber
659  * @param {WebInspector.ConsoleMessage} originalMessage
660  */
661 WebInspector.PresentationConsoleMessage = function(uiSourceCode, lineNumber, originalMessage)
662 {
663     this.uiSourceCode = uiSourceCode;
664     this.lineNumber = lineNumber;
665     this.originalMessage = originalMessage;
666 }
667
668 /**
669  * @constructor
670  * @param {DebuggerAgent.CallFrame} callFrame
671  * @param {number} index
672  * @param {WebInspector.DebuggerPresentationModel} model
673  * @param {WebInspector.RawSourceCode} rawSourceCode
674  */
675 WebInspector.PresentationCallFrame = function(callFrame, index, model, rawSourceCode)
676 {
677     this._callFrame = callFrame;
678     this._index = index;
679     this._model = model;
680     this._rawSourceCode = rawSourceCode;
681 }
682
683 WebInspector.PresentationCallFrame.prototype = {
684     /**
685      * @return {string}
686      */
687     get type()
688     {
689         return this._callFrame.type;
690     },
691
692     /**
693      * @return {Array.<DebuggerAgent.Scope>}
694      */
695     get scopeChain()
696     {
697         return this._callFrame.scopeChain;
698     },
699
700     /**
701      * @return {RuntimeAgent.RemoteObject}
702      */
703     get this()
704     {
705         return this._callFrame.this;
706     },
707
708     /**
709      * @return {number}
710      */
711     get index()
712     {
713         return this._index;
714     },
715
716     /**
717      * @return {WebInspector.RawSourceCode}
718      */
719     get rawSourceCode()
720     {
721         return this._rawSourceCode;
722     },
723
724     /**
725      * @param {string} code
726      * @param {string} objectGroup
727      * @param {boolean} includeCommandLineAPI
728      * @param {boolean} returnByValue
729      * @param {function(?RuntimeAgent.RemoteObject, boolean)=} callback
730      */
731     evaluate: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback)
732     {
733         /**
734          * @this {WebInspector.PresentationCallFrame}
735          * @param {?Protocol.Error} error
736          * @param {RuntimeAgent.RemoteObject} result
737          * @param {boolean} wasThrown
738          */
739         function didEvaluateOnCallFrame(error, result, wasThrown)
740         {
741             if (error) {
742                 console.error(error);
743                 callback(null, false);
744                 return;
745             }
746             callback(result, wasThrown);
747         }
748         DebuggerAgent.evaluateOnCallFrame(this._callFrame.callFrameId, code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluateOnCallFrame.bind(this));
749     },
750
751     /**
752      * @param {function(WebInspector.UILocation)} callback
753      */
754     uiLocation: function(callback)
755     {
756         function sourceMappingReady()
757         {
758             this._rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this);
759             callback(this._rawSourceCode.sourceMapping.rawLocationToUILocation(this._callFrame.location));
760         }
761         if (this._rawSourceCode.sourceMapping)
762             sourceMappingReady.call(this);
763         else
764             this._rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this);
765     }
766 }
767
768 /**
769  * @constructor
770  * @implements {WebInspector.ResourceDomainModelBinding}
771  * @param {WebInspector.DebuggerPresentationModel} model
772  */
773 WebInspector.DebuggerPresentationModelResourceBinding = function(model)
774 {
775     this._presentationModel = model;
776     WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Script, this);
777 }
778
779 WebInspector.DebuggerPresentationModelResourceBinding.prototype = {
780     /**
781      * @param {WebInspector.Resource} resource
782      */
783     canSetContent: function(resource)
784     {
785         var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url)
786         if (!rawSourceCode)
787             return false;
788         return this._presentationModel.canEditScriptSource(rawSourceCode.sourceMapping.uiSourceCodeList()[0]);
789     },
790
791     /**
792      * @param {WebInspector.Resource} resource
793      * @param {string} content
794      * @param {boolean} majorChange
795      * @param {function(?Protocol.Error)} userCallback
796      */
797     setContent: function(resource, content, majorChange, userCallback)
798     {
799         if (!majorChange)
800             return;
801
802         var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url);
803         if (!rawSourceCode) {
804             userCallback("Resource is not editable");
805             return;
806         }
807
808         resource.requestContent(this._setContentWithInitialContent.bind(this, rawSourceCode.sourceMapping.uiSourceCodeList()[0], content, userCallback));
809     },
810
811     /**
812      * @param {WebInspector.UISourceCode} uiSourceCode
813      * @param {string} content
814      * @param {function(?Protocol.Error)} userCallback
815      * @param {string} oldContent
816      */
817     _setContentWithInitialContent: function(uiSourceCode, content, userCallback, oldContent)
818     {
819         /**
820          * @this {WebInspector.DebuggerPresentationModelResourceBinding}
821          * @param {?Protocol.Error} error
822          */
823         function callback(error)
824         {
825             if (userCallback)
826                 userCallback(error);
827             if (!error)
828                 this._presentationModel._updateBreakpointsAfterLiveEdit(uiSourceCode, oldContent, content);
829         }
830         this._presentationModel.setScriptSource(uiSourceCode, content, callback.bind(this));
831     }
832 }
833
834 /**
835  * @interface
836  */
837 WebInspector.DebuggerPresentationModel.LinkifierFormatter = function()
838 {
839 }
840
841 WebInspector.DebuggerPresentationModel.LinkifierFormatter.prototype = {
842     /**
843      * @param {WebInspector.RawSourceCode} rawSourceCode
844      * @param {Element} anchor
845      */
846     formatRawSourceCodeAnchor: function(rawSourceCode, anchor) { },
847 }
848
849 /**
850  * @constructor
851  * @implements {WebInspector.DebuggerPresentationModel.LinkifierFormatter}
852  * @param {number=} maxLength
853  */
854 WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter = function(maxLength)
855 {
856     this._maxLength = maxLength;
857 }
858
859 WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter.prototype = {
860     /**
861      * @param {WebInspector.RawSourceCode} rawSourceCode
862      * @param {Element} anchor
863      */
864     formatRawSourceCodeAnchor: function(rawSourceCode, anchor)
865     {
866         var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(anchor.rawLocation);
867
868         anchor.textContent = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber);
869             
870         var text = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber);
871         if (this._maxLength)
872             text = text.trimMiddle(this._maxLength);
873         anchor.textContent = text;
874     }
875 }
876
877 WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter.prototype.__proto__ = WebInspector.DebuggerPresentationModel.LinkifierFormatter.prototype;
878
879 /**
880  * @constructor
881  * @param {WebInspector.DebuggerPresentationModel} model
882  * @param {WebInspector.DebuggerPresentationModel.LinkifierFormatter=} formatter
883  */
884 WebInspector.DebuggerPresentationModel.Linkifier = function(model, formatter)
885 {
886     this._model = model;
887     this._formatter = formatter || new WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter();
888     this._anchorsForRawSourceCode = {};
889 }
890
891 WebInspector.DebuggerPresentationModel.Linkifier.prototype = {
892     /**
893      * @param {string} sourceURL
894      * @param {number=} lineNumber
895      * @param {number=} columnNumber
896      * @param {string=} classes
897      */
898     linkifyLocation: function(sourceURL, lineNumber, columnNumber, classes)
899     {
900         var rawSourceCode = this._model._rawSourceCodeForScriptWithURL(sourceURL);
901         if (!rawSourceCode)
902             return this.linkifyResource(sourceURL, lineNumber, classes);
903         
904         return this.linkifyRawSourceCode(rawSourceCode, lineNumber, columnNumber, classes);
905     },
906
907     /**
908      * @param {string} sourceURL
909      * @param {number=} lineNumber
910      * @param {string=} classes
911      */
912     linkifyResource: function(sourceURL, lineNumber, classes)
913     {
914         var linkText = WebInspector.formatLinkText(sourceURL, lineNumber);
915         var anchor = WebInspector.linkifyURLAsNode(sourceURL, linkText, classes, false);
916         anchor.setAttribute("preferred_panel", "resources");
917         anchor.setAttribute("line_number", lineNumber);
918         return anchor;
919     },
920
921     /**
922      * @param {WebInspector.RawSourceCode} rawSourceCode
923      * @param {number=} lineNumber
924      * @param {number=} columnNumber
925      * @param {string=} classes
926      */
927     linkifyRawSourceCode: function(rawSourceCode, lineNumber, columnNumber, classes)
928     {
929         var anchor = WebInspector.linkifyURLAsNode(rawSourceCode.url, "", classes, false);
930         anchor.rawLocation = { lineNumber: lineNumber, columnNumber: columnNumber };
931
932         var anchors = this._anchorsForRawSourceCode[rawSourceCode.id];
933         if (!anchors) {
934             anchors = [];
935             this._anchorsForRawSourceCode[rawSourceCode.id] = anchors;
936             rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this);
937         }
938
939         if (rawSourceCode.sourceMapping)
940             this._updateAnchor(rawSourceCode, anchor);
941         anchors.push(anchor);
942         return anchor;
943     },
944
945     reset: function()
946     {
947         for (var id in this._anchorsForRawSourceCode) {
948             if (this._model._rawSourceCode[id]) // In case of navigation the list of rawSourceCodes is empty.
949                 this._model._rawSourceCode[id].removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this);
950         }
951         this._anchorsForRawSourceCode = {};
952     },
953
954     /**
955      * @param {WebInspector.Event} event
956      */
957     _updateSourceAnchors: function(event)
958     {
959         var rawSourceCode = /** @type {WebInspector.RawSourceCode} */ event.target;
960         var anchors = this._anchorsForRawSourceCode[rawSourceCode.id];
961         for (var i = 0; i < anchors.length; ++i)
962             this._updateAnchor(rawSourceCode, anchors[i]);
963     },
964
965     /**
966      * @param {WebInspector.RawSourceCode} rawSourceCode
967      * @param {Element} anchor
968      */
969     _updateAnchor: function(rawSourceCode, anchor)
970     {
971         var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(anchor.rawLocation);
972         anchor.setAttribute("preferred_panel", "scripts");
973         anchor.uiSourceCode = uiLocation.uiSourceCode;
974         anchor.lineNumber = uiLocation.lineNumber;
975         
976         this._formatter.formatRawSourceCodeAnchor(rawSourceCode, anchor);
977     }
978 }
979
980 WebInspector.DebuggerPresentationModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype;
981
982 /**
983  * @type {?WebInspector.DebuggerPresentationModel}
984  */
985 WebInspector.debuggerPresentationModel = null;